Skip to content

Commit

Permalink
feat: add remote reusable workflows
Browse files Browse the repository at this point in the history
This changes adds cloning of a remote repository to
run a workflow included in it.

Closes nektos#826
  • Loading branch information
KnisterPeter authored and github-actions committed Jan 14, 2023
1 parent 439c1fe commit 935b00b
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 8 deletions.
62 changes: 58 additions & 4 deletions pkg/runner/reusable_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,46 @@ package runner
import (
"fmt"
"path"
"regexp"
"strings"

"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/common/git"
"github.com/nektos/act/pkg/model"
)

func newLocalReusableWorkflowExecutor(rc *RunContext) common.Executor {
return newReusableWorkflowExecutor(rc, rc.Config.Workdir)
return newReusableWorkflowExecutor(rc, rc.Config.Workdir, rc.Run.Job().Uses)
}

func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor {
return common.NewErrorExecutor(fmt.Errorf("remote reusable workflows are currently not supported (see https://github.com/nektos/act/issues/826 for updates)"))
uses := rc.Run.Job().Uses

remoteReusableWorkflow := newRemoteReusableWorkflow(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))
}

remoteReusableWorkflow.URL = rc.Config.GitHubInstance

// todo: really use rc.ActionCacheDir()?
workflowDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), strings.ReplaceAll(uses, "/", "-"))

gitClone := git.NewGitCloneExecutor(git.NewGitCloneExecutorInput{
URL: remoteReusableWorkflow.CloneURL(),
Ref: remoteReusableWorkflow.Ref,
Dir: workflowDir,
Token: rc.Config.Token,
})

return common.NewPipelineExecutor(
gitClone,
newReusableWorkflowExecutor(rc, workflowDir, fmt.Sprintf("./.github/workflows/%s", remoteReusableWorkflow.Filename)),
)
}

func newReusableWorkflowExecutor(rc *RunContext, directory string) common.Executor {
planner, err := model.NewWorkflowPlanner(path.Join(directory, rc.Run.Job().Uses), true)
func newReusableWorkflowExecutor(rc *RunContext, directory string, workflow string) common.Executor {
planner, err := model.NewWorkflowPlanner(path.Join(directory, workflow), true)
if err != nil {
return common.NewErrorExecutor(err)
}
Expand All @@ -43,3 +68,32 @@ func NewReusableWorkflowRunner(rc *RunContext) (Runner, error) {

return runner.configure()
}

type remoteReusableWorkflow struct {
URL string
Org string
Repo string
Filename string
Ref string
}

func (r *remoteReusableWorkflow) CloneURL() string {
return fmt.Sprintf("https://%s/%s/%s", r.URL, r.Org, r.Repo)
}

func newRemoteReusableWorkflow(uses string) *remoteReusableWorkflow {
// GitHub docs:
// https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses
r := regexp.MustCompile(`^([^/]+)/([^/]+)/.github/workflows/([^@]+)@(.*)$`)
matches := r.FindStringSubmatch(uses)
if len(matches) != 5 {
return nil
}
return &remoteReusableWorkflow{
Org: matches[1],
Repo: matches[2],
Filename: matches[3],
Ref: matches[4],
URL: "github.com",
}
}
2 changes: 1 addition & 1 deletion pkg/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func TestRunEvent(t *testing.T) {
{workdir, "uses-composite-with-error", "push", "Job 'failing-composite-action' failed", platforms, secrets},
{workdir, "uses-nested-composite", "push", "", platforms, secrets},
{workdir, "remote-action-composite-js-pre-with-defaults", "push", "", platforms, secrets},
{workdir, "uses-workflow", "push", "reusable workflows are currently not supported (see https://github.com/nektos/act/issues/826 for updates)", platforms, secrets},
{workdir, "uses-workflow", "push", "", platforms, map[string]string{"secret": "keep_it_private"}},
{workdir, "uses-workflow", "pull_request", "", platforms, map[string]string{"secret": "keep_it_private"}},
{workdir, "uses-docker-url", "push", "", platforms, secrets},
{workdir, "act-composite-env-test", "push", "", platforms, secrets},
Expand Down
32 changes: 29 additions & 3 deletions pkg/runner/testdata/uses-workflow/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,34 @@ on: push

jobs:
reusable-workflow:
uses: nektos/act-tests/.github/workflows/reusable-workflow.yml@master
uses: nektos/act-test-actions/.github/workflows/reusable-workflow.yml@main
with:
username: mona
string_required: string
bool_required: ${{ true }}
number_required: 1
secrets:
envPATH: ${{ secrets.envPAT }}
secret: keep_it_private

reusable-workflow-with-inherited-secrets:
uses: nektos/act-test-actions/.github/workflows/reusable-workflow.yml@main
with:
string_required: string
bool_required: ${{ true }}
number_required: 1
secrets: inherit

output-test:
runs-on: ubuntu-latest
needs:
- reusable-workflow
- reusable-workflow-with-inherited-secrets
steps:
- name: output with secrets map
run: |
echo reusable-workflow.output=${{ needs.reusable-workflow.outputs.output }}
[[ "${{ needs.reusable-workflow.outputs.output == 'string' }}" = "true" ]] || exit 1
- name: output with inherited secrets
run: |
echo reusable-workflow-with-inherited-secrets.output=${{ needs.reusable-workflow-with-inherited-secrets.outputs.output }}
[[ "${{ needs.reusable-workflow-with-inherited-secrets.outputs.output == 'string' }}" = "true" ]] || exit 1

0 comments on commit 935b00b

Please sign in to comment.