From 5e76853b55ab6297090450dccaade27d80870bbd Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Wed, 29 Mar 2023 13:59:22 +0800 Subject: [PATCH] Support reusable workflow (#34) Fix https://gitea.com/gitea/act_runner/issues/80 Fix https://gitea.com/gitea/act_runner/issues/85 To support reusable workflows, I made some improvements: - read `yml` files from both `.gitea/workflows` and `.github/workflows` - clone repository for local reusable workflows because the runner doesn't have the code in its local directory - fix the incorrect clone url like `https://https://gitea.com` Co-authored-by: Jason Song Reviewed-on: https://gitea.com/gitea/act/pulls/34 Reviewed-by: Jason Song Co-authored-by: Zettat123 Co-committed-by: Zettat123 --- pkg/model/workflow.go | 4 +++ pkg/runner/reusable_workflow.go | 61 ++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/pkg/model/workflow.go b/pkg/model/workflow.go index 9c05bcb..38aed62 100644 --- a/pkg/model/workflow.go +++ b/pkg/model/workflow.go @@ -506,8 +506,12 @@ func (j JobType) String() string { func (j *Job) Type() JobType { if strings.HasPrefix(j.Uses, "./.github/workflows") && (strings.HasSuffix(j.Uses, ".yml") || strings.HasSuffix(j.Uses, ".yaml")) { return JobTypeReusableWorkflowLocal + } else if strings.HasPrefix(j.Uses, "./.gitea/workflows") && (strings.HasSuffix(j.Uses, ".yml") || strings.HasSuffix(j.Uses, ".yaml")) { + return JobTypeReusableWorkflowLocal } else if !strings.HasPrefix(j.Uses, "./") && strings.Contains(j.Uses, ".github/workflows") && (strings.Contains(j.Uses, ".yml@") || strings.Contains(j.Uses, ".yaml@")) { return JobTypeReusableWorkflowRemote + } else if !strings.HasPrefix(j.Uses, "./") && strings.Contains(j.Uses, ".gitea/workflows") && (strings.Contains(j.Uses, ".yml@") || strings.Contains(j.Uses, ".yaml@")) { + return JobTypeReusableWorkflowRemote } return JobTypeDefault } diff --git a/pkg/runner/reusable_workflow.go b/pkg/runner/reusable_workflow.go index 1ffa22b..83fac3e 100644 --- a/pkg/runner/reusable_workflow.go +++ b/pkg/runner/reusable_workflow.go @@ -8,6 +8,7 @@ import ( "os" "path" "regexp" + "strings" "sync" "github.com/nektos/act/pkg/common" @@ -16,15 +17,14 @@ import ( ) func newLocalReusableWorkflowExecutor(rc *RunContext) common.Executor { - return newReusableWorkflowExecutor(rc, rc.Config.Workdir, rc.Run.Job().Uses) -} + // ./.gitea/workflows/wf.yml -> .gitea/workflows/wf.yml + trimmedUses := strings.TrimPrefix(rc.Run.Job().Uses, "./") + // uses string format is {owner}/{repo}/.{git_platform}/workflows/{filename}@{ref} + uses := fmt.Sprintf("%s/%s@%s", rc.Config.PresetGitHubContext.Repository, trimmedUses, rc.Config.PresetGitHubContext.Sha) -func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor { - uses := rc.Run.Job().Uses - - remoteReusableWorkflow := newRemoteReusableWorkflow(uses) + remoteReusableWorkflow := newRemoteReusableWorkflowWithPlat(uses) if remoteReusableWorkflow == nil { - return common.NewErrorExecutor(fmt.Errorf("expected format {owner}/{repo}/.github/workflows/{filename}@{ref}. Actual '%s' Input string was not in a correct format", uses)) + return common.NewErrorExecutor(fmt.Errorf("expected format {owner}/{repo}/.{git_platform}/workflows/{filename}@{ref}. Actual '%s' Input string was not in a correct format", uses)) } remoteReusableWorkflow.URL = rc.Config.GitHubInstance @@ -32,7 +32,24 @@ func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor { return common.NewPipelineExecutor( newMutexExecutor(cloneIfRequired(rc, *remoteReusableWorkflow, workflowDir)), - newReusableWorkflowExecutor(rc, workflowDir, fmt.Sprintf("./.github/workflows/%s", remoteReusableWorkflow.Filename)), + newReusableWorkflowExecutor(rc, workflowDir, remoteReusableWorkflow.FilePath()), + ) +} + +func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor { + uses := rc.Run.Job().Uses + + remoteReusableWorkflow := newRemoteReusableWorkflowWithPlat(uses) + if remoteReusableWorkflow == nil { + return common.NewErrorExecutor(fmt.Errorf("expected format {owner}/{repo}/.{git_platform}/workflows/{filename}@{ref}. Actual '%s' Input string was not in a correct format", uses)) + } + remoteReusableWorkflow.URL = rc.Config.GitHubInstance + + workflowDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(uses)) + + return common.NewPipelineExecutor( + newMutexExecutor(cloneIfRequired(rc, *remoteReusableWorkflow, workflowDir)), + newReusableWorkflowExecutor(rc, workflowDir, remoteReusableWorkflow.FilePath()), ) } @@ -105,12 +122,40 @@ type remoteReusableWorkflow struct { Repo string Filename string Ref string + + GitPlatform string } func (r *remoteReusableWorkflow) CloneURL() string { + // In Gitea, r.URL always has the protocol prefix, we don't need to add extra prefix in this case. + if strings.HasPrefix(r.URL, "http://") || strings.HasPrefix(r.URL, "https://") { + return fmt.Sprintf("%s/%s/%s", r.URL, r.Org, r.Repo) + } return fmt.Sprintf("https://%s/%s/%s", r.URL, r.Org, r.Repo) } +func (r *remoteReusableWorkflow) FilePath() string { + return fmt.Sprintf("./.%s/workflows/%s", r.GitPlatform, r.Filename) +} + +func newRemoteReusableWorkflowWithPlat(uses string) *remoteReusableWorkflow { + // GitHub docs: + // https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses + r := regexp.MustCompile(`^([^/]+)/([^/]+)/\.([^/]+)/workflows/([^@]+)@(.*)$`) + matches := r.FindStringSubmatch(uses) + if len(matches) != 6 { + return nil + } + return &remoteReusableWorkflow{ + Org: matches[1], + Repo: matches[2], + GitPlatform: matches[3], + Filename: matches[4], + Ref: matches[5], + } +} + +// deprecated: use newRemoteReusableWorkflowWithPlat func newRemoteReusableWorkflow(uses string) *remoteReusableWorkflow { // GitHub docs: // https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses