// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package run import ( "context" "encoding/json" "fmt" "maps" "path/filepath" "strings" "sync" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "connectrpc.com/connect" "github.com/actions-oss/act-cli/pkg/artifactcache" "github.com/actions-oss/act-cli/pkg/model" "github.com/actions-oss/act-cli/pkg/runner" "github.com/actions-oss/act-cli/pkg/schema" "github.com/docker/docker/api/types/container" log "github.com/sirupsen/logrus" "gitea.com/gitea/act_runner/internal/pkg/client" "gitea.com/gitea/act_runner/internal/pkg/config" "gitea.com/gitea/act_runner/internal/pkg/labels" "gitea.com/gitea/act_runner/internal/pkg/report" "gitea.com/gitea/act_runner/internal/pkg/ver" ) // Runner runs the pipeline. type Runner struct { name string cfg *config.Config client client.Client labels labels.Labels envs map[string]string runningTasks sync.Map } func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner { ls := labels.Labels{} for _, v := range reg.Labels { if l, err := labels.Parse(v); err == nil { ls = append(ls, l) } } envs := make(map[string]string, len(cfg.Runner.Envs)) maps.Copy(envs, cfg.Runner.Envs) if cfg.Cache.Enabled == nil || *cfg.Cache.Enabled { if cfg.Cache.ExternalServer != "" { envs["ACTIONS_CACHE_URL"] = cfg.Cache.ExternalServer } else { cacheHandler, err := artifactcache.StartHandler( cfg.Cache.Dir, cfg.Cache.Host, cfg.Cache.Port, log.StandardLogger().WithField("module", "cache_request"), ) if err != nil { log.Errorf("cannot init cache server, it will be disabled: %v", err) // go on } else { envs["ACTIONS_CACHE_URL"] = cacheHandler.ExternalURL() + "/" } } } // set artifact gitea api artifactGiteaAPI := strings.TrimSuffix(cli.Address(), "/") + "/api/actions_pipeline/" envs["ACTIONS_RUNTIME_URL"] = artifactGiteaAPI envs["ACTIONS_RESULTS_URL"] = strings.TrimSuffix(cli.Address(), "/") // Set specific environments to distinguish between Gitea and GitHub envs["GITEA_ACTIONS"] = "true" envs["GITEA_ACTIONS_RUNNER_VERSION"] = ver.Version() return &Runner{ name: reg.Name, cfg: cfg, client: cli, labels: ls, envs: envs, } } func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error { if _, ok := r.runningTasks.Load(task.Id); ok { return fmt.Errorf("task %d is already running", task.Id) } r.runningTasks.Store(task.Id, struct{}{}) defer r.runningTasks.Delete(task.Id) ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout) defer cancel() reporter := report.NewReporter(ctx, cancel, r.client, task) var runErr error defer func() { lastWords := "" if runErr != nil { lastWords = runErr.Error() } _ = reporter.Close(lastWords) }() reporter.RunDaemon() runErr = r.run(ctx, task, reporter) return nil } // getDefaultActionsURL // when DEFAULT_ACTIONS_URL == "https://github.com" and GithubMirror is not blank, // it should be set to GithubMirror first. func (r *Runner) getDefaultActionsURL(task *runnerv1.Task) string { giteaDefaultActionsURL := task.Context.Fields["gitea_default_actions_url"].GetStringValue() if giteaDefaultActionsURL == "https://github.com" && r.cfg.Runner.GithubMirror != "" { return r.cfg.Runner.GithubMirror } return giteaDefaultActionsURL } func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.Reporter) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic: %v", r) } }() reporter.Logf("%s(version:%s) received task %v of job %v, be triggered by event: %s", r.name, ver.Version(), task.Id, task.Context.Fields["job"].GetStringValue(), task.Context.Fields["event_name"].GetStringValue()) workflow, jobID, err := generateWorkflow(task) if err != nil { return err } // TODO GITEA plan, err := model.CombineWorkflowPlanner(workflow).PlanJob(jobID) if err != nil { return err } job := workflow.GetJob(jobID) var stepIds []string for i, v := range job.Steps { if v.ID == "" { v.ID = fmt.Sprint(i) } stepIds = append(stepIds, v.ID) } reporter.SetStepIdMapping(stepIds...) taskContext := task.Context.Fields log.Infof("task %v repo is %v %v %v", task.Id, taskContext["repository"].GetStringValue(), r.getDefaultActionsURL(task), r.client.Address()) preset := &model.GithubContext{ Event: taskContext["event"].GetStructValue().AsMap(), RunID: taskContext["run_id"].GetStringValue(), RunNumber: taskContext["run_number"].GetStringValue(), Actor: taskContext["actor"].GetStringValue(), Repository: taskContext["repository"].GetStringValue(), EventName: taskContext["event_name"].GetStringValue(), Sha: taskContext["sha"].GetStringValue(), Ref: taskContext["ref"].GetStringValue(), RefName: taskContext["ref_name"].GetStringValue(), RefType: taskContext["ref_type"].GetStringValue(), HeadRef: taskContext["head_ref"].GetStringValue(), BaseRef: taskContext["base_ref"].GetStringValue(), Token: taskContext["token"].GetStringValue(), RepositoryOwner: taskContext["repository_owner"].GetStringValue(), RetentionDays: taskContext["retention_days"].GetStringValue(), } if t := task.Secrets["GITEA_TOKEN"]; t != "" { preset.Token = t } else if t := task.Secrets["GITHUB_TOKEN"]; t != "" { preset.Token = t } if actionsIDTokenRequestURL := taskContext["actions_id_token_request_url"].GetStringValue(); actionsIDTokenRequestURL != "" { r.envs["ACTIONS_ID_TOKEN_REQUEST_URL"] = actionsIDTokenRequestURL r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = taskContext["actions_id_token_request_token"].GetStringValue() task.Secrets["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] } giteaRuntimeToken := taskContext["gitea_runtime_token"].GetStringValue() if giteaRuntimeToken == "" { // use task token to action api token for previous Gitea Server Versions giteaRuntimeToken = preset.Token } r.envs["ACTIONS_RUNTIME_TOKEN"] = giteaRuntimeToken // TODO GITEA eventJSON, err := json.Marshal(preset.Event) if err != nil { return err } // maxLifetime := 3 * time.Hour // if deadline, ok := ctx.Deadline(); ok { // maxLifetime = time.Until(deadline) // } actCtx := map[string]interface{}{} forgeCtx := task.Context.AsMap() actCtx["github"] = forgeCtx actCtx["gitea"] = forgeCtx runnerConfig := &runner.Config{ // On Linux, Workdir will be like "///" // On Windows, Workdir will be like "\\\" Workdir: filepath.FromSlash(fmt.Sprintf("/%s/%s", strings.TrimLeft(r.cfg.Container.WorkdirParent, "/"), preset.Repository)), BindWorkdir: false, ActionCacheDir: filepath.FromSlash(r.cfg.Host.WorkdirParent), ReuseContainers: false, ForcePull: r.cfg.Container.ForcePull, ForceRebuild: r.cfg.Container.ForceRebuild, LogOutput: true, JSONLogger: false, Env: r.envs, Secrets: task.Secrets, // GitHubInstance: strings.TrimSuffix(r.client.Address(), "/"), AutoRemove: true, NoSkipCheckout: true, // TODO GITEA // PresetGitHubContext: preset, EventJSON: string(eventJSON), // ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id), // ContainerMaxLifetime: maxLifetime, ContainerNetworkMode: container.NetworkMode(r.cfg.Container.Network), ContainerOptions: r.cfg.Container.Options, ContainerDaemonSocket: r.cfg.Container.DockerHost, Privileged: r.cfg.Container.Privileged, Platforms: map[string]string{ "dummy": "-self-hosted", }, // TODO GITEA // DefaultActionInstance: r.getDefaultActionsURL(task), // PlatformPicker: r.labels.PickPlatform, Vars: task.Vars, // TODO GITEA // ValidVolumes: r.cfg.Container.ValidVolumes, // InsecureSkipTLS: r.cfg.Runner.Insecure, GitHubServerURL: strings.TrimSuffix(r.client.Address(), "/"), GitHubAPIServerURL: strings.TrimSuffix(r.client.Address(), "/") + "/api/v1", // Invalid but ok GitHubGraphQlAPIServerURL: strings.TrimSuffix(r.client.Address(), "/api/graphql"), MainContextNames: []string{"gitea", "github"}, Action: model.ActionConfig{ Schema: schema.GetGiteaActionSchema(), }, ContextData: actCtx, } rr, err := runner.New(runnerConfig) if err != nil { return err } executor := rr.NewPlanExecutor(plan) reporter.Logf("workflow prepared") // TODO GITEA ctx = runner.WithJobLoggerFactory(ctx, &JobLoggerWithReporter{Reporter: reporter, LogToTerminal: log.IsLevelEnabled(log.DebugLevel)}) execErr := executor(ctx) reporter.SetOutputs(job.Outputs) return execErr } func (r *Runner) Declare(ctx context.Context, labels []string) (*connect.Response[runnerv1.DeclareResponse], error) { return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{ Version: ver.Version(), Labels: labels, })) }