feat: add option to bypass GHE for actions checkout (#1162)

* feat(#1161): add --through-action to assigned actions from GitHub

* docs(flags): add --through-action and --through-action-token flags description

* test(action, remote): add test case for ThroughAction

* refactor(command): rename command from --through-action to --actions-from-github

* refactor(command): rename command from --actions-from-github to --replace-ghe-action-with-github-com
This commit is contained in:
Neo Hsu 2022-06-21 21:52:21 +08:00 committed by GitHub
parent 9d7595ab11
commit de37f75077
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 286 additions and 127 deletions

View file

@ -159,42 +159,44 @@ It will save that information to `~/.actrc`, please refer to [Configuration](#co
# Flags # Flags
```none ```none
-a, --actor string user that triggered the event (default "nektos/act") -a, --actor string user that triggered the event (default "nektos/act")
--artifact-server-path string Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start. --replace-ghe-action-with-github-com If you are using GitHub Enterprise Server and allow specified actions from GitHub (github.com), you can set actions on this. (e.g. --replace-ghe-action-with-github-com=github/super-linter)
--artifact-server-port string Defines the port where the artifact server listens (will only bind to localhost). (default "34567") --replace-ghe-action-token-with-github-com If you are using replace-ghe-action-with-github-com and you want to use private actions on GitHub, you have to set personal access token
-b, --bind bind working directory to container, rather than copy --artifact-server-path string Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start.
--container-architecture string Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms. --artifact-server-port string Defines the port where the artifact server listens (will only bind to localhost). (default "34567")
--container-cap-add stringArray kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE) -b, --bind bind working directory to container, rather than copy
--container-cap-drop stringArray kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE) --container-architecture string Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms.
--container-daemon-socket string Path to Docker daemon socket which will be mounted to containers (default "/var/run/docker.sock") --container-cap-add stringArray kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)
--defaultbranch string the name of the main branch --container-cap-drop stringArray kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)
--detect-event Use first event type from workflow as event that triggered the workflow --container-daemon-socket string Path to Docker daemon socket which will be mounted to containers (default "/var/run/docker.sock")
-C, --directory string working directory (default ".") --defaultbranch string the name of the main branch
-n, --dryrun dryrun mode --detect-event Use first event type from workflow as event that triggered the workflow
--env stringArray env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv) -C, --directory string working directory (default ".")
--env-file string environment file to read and use as env in the containers (default ".env") -n, --dryrun dryrun mode
-e, --eventpath string path to event JSON file --env stringArray env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)
--github-instance string GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server. (default "github.com") --env-file string environment file to read and use as env in the containers (default ".env")
-g, --graph draw workflows -e, --eventpath string path to event JSON file
-h, --help help for act --github-instance string GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server. (default "github.com")
--insecure-secrets NOT RECOMMENDED! Doesn't hide secrets while printing logs. -g, --graph draw workflows
-j, --job string run job -h, --help help for act
-l, --list list workflows --insecure-secrets NOT RECOMMENDED! Doesn't hide secrets while printing logs.
--no-recurse Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag -j, --job string run job
-P, --platform stringArray custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04) -l, --list list workflows
--privileged use privileged mode --no-recurse Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag
-p, --pull pull docker image(s) even if already present -P, --platform stringArray custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)
-q, --quiet disable logging of output from steps --privileged use privileged mode
--rebuild rebuild local action docker image(s) even if already present -p, --pull pull docker image(s) even if already present
-r, --reuse don't remove container(s) on successfully completed workflow(s) to maintain state between runs -q, --quiet disable logging of output from steps
--rm automatically remove container(s)/volume(s) after a workflow(s) failure --rebuild rebuild local action docker image(s) even if already present
-s, --secret stringArray secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret) -r, --reuse don't remove container(s) on successfully completed workflow(s) to maintain state between runs
--secret-file string file with list of secrets to read from (e.g. --secret-file .secrets) (default ".secrets") --rm automatically remove container(s)/volume(s) after a workflow(s) failure
--use-gitignore Controls whether paths specified in .gitignore should be copied into container (default true) -s, --secret stringArray secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)
--userns string user namespace to use --secret-file string file with list of secrets to read from (e.g. --secret-file .secrets) (default ".secrets")
-v, --verbose verbose output --use-gitignore Controls whether paths specified in .gitignore should be copied into container (default true)
-w, --watch watch the contents of the local repo and run when files change --userns string user namespace to use
-W, --workflows string path to workflow file(s) (default "./.github/workflows/") -v, --verbose verbose output
-w, --watch watch the contents of the local repo and run when files change
-W, --workflows string path to workflow file(s) (default "./.github/workflows/")
``` ```
## `GITHUB_TOKEN` ## `GITHUB_TOKEN`

View file

@ -8,39 +8,41 @@ import (
// Input contains the input for the root command // Input contains the input for the root command
type Input struct { type Input struct {
actor string actor string
workdir string workdir string
workflowsPath string workflowsPath string
autodetectEvent bool autodetectEvent bool
eventPath string eventPath string
reuseContainers bool reuseContainers bool
bindWorkdir bool bindWorkdir bool
secrets []string secrets []string
envs []string envs []string
platforms []string platforms []string
dryrun bool dryrun bool
forcePull bool forcePull bool
forceRebuild bool forceRebuild bool
noOutput bool noOutput bool
envfile string envfile string
secretfile string secretfile string
insecureSecrets bool insecureSecrets bool
defaultBranch string defaultBranch string
privileged bool privileged bool
usernsMode string usernsMode string
containerArchitecture string containerArchitecture string
containerDaemonSocket string containerDaemonSocket string
noWorkflowRecurse bool noWorkflowRecurse bool
useGitIgnore bool useGitIgnore bool
githubInstance string githubInstance string
containerCapAdd []string containerCapAdd []string
containerCapDrop []string containerCapDrop []string
autoRemove bool autoRemove bool
artifactServerPath string artifactServerPath string
artifactServerPort string artifactServerPort string
jsonLogger bool jsonLogger bool
noSkipCheckout bool noSkipCheckout bool
remoteName string remoteName string
replaceGheActionWithGithubCom []string
replaceGheActionTokenWithGithubCom string
} }
func (i *Input) resolve(path string) string { func (i *Input) resolve(path string) string {

View file

@ -61,6 +61,8 @@ func Execute(ctx context.Context, version string) {
rootCmd.Flags().StringArrayVarP(&input.containerCapAdd, "container-cap-add", "", []string{}, "kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)") rootCmd.Flags().StringArrayVarP(&input.containerCapAdd, "container-cap-add", "", []string{}, "kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)")
rootCmd.Flags().StringArrayVarP(&input.containerCapDrop, "container-cap-drop", "", []string{}, "kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)") rootCmd.Flags().StringArrayVarP(&input.containerCapDrop, "container-cap-drop", "", []string{}, "kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)")
rootCmd.Flags().BoolVar(&input.autoRemove, "rm", false, "automatically remove container(s)/volume(s) after a workflow(s) failure") rootCmd.Flags().BoolVar(&input.autoRemove, "rm", false, "automatically remove container(s)/volume(s) after a workflow(s) failure")
rootCmd.Flags().StringArrayVarP(&input.replaceGheActionWithGithubCom, "replace-ghe-action-with-github-com", "", []string{}, "If you are using GitHub Enterprise Server and allow specified actions from GitHub (github.com), you can set actions on this. (e.g. --replace-ghe-action-with-github-com =github/super-linter)")
rootCmd.Flags().StringVar(&input.replaceGheActionTokenWithGithubCom, "replace-ghe-action-token-with-github-com", "", "If you are using replace-ghe-action-with-github-com and you want to use private actions on GitHub, you have to set personal access token")
rootCmd.PersistentFlags().StringVarP(&input.actor, "actor", "a", "nektos/act", "user that triggered the event") rootCmd.PersistentFlags().StringVarP(&input.actor, "actor", "a", "nektos/act", "user that triggered the event")
rootCmd.PersistentFlags().StringVarP(&input.workflowsPath, "workflows", "W", "./.github/workflows/", "path to workflow file(s)") rootCmd.PersistentFlags().StringVarP(&input.workflowsPath, "workflows", "W", "./.github/workflows/", "path to workflow file(s)")
rootCmd.PersistentFlags().BoolVarP(&input.noWorkflowRecurse, "no-recurse", "", false, "Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag") rootCmd.PersistentFlags().BoolVarP(&input.noWorkflowRecurse, "no-recurse", "", false, "Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag")
@ -370,35 +372,37 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
// run the plan // run the plan
config := &runner.Config{ config := &runner.Config{
Actor: input.actor, Actor: input.actor,
EventName: eventName, EventName: eventName,
EventPath: input.EventPath(), EventPath: input.EventPath(),
DefaultBranch: defaultbranch, DefaultBranch: defaultbranch,
ForcePull: input.forcePull, ForcePull: input.forcePull,
ForceRebuild: input.forceRebuild, ForceRebuild: input.forceRebuild,
ReuseContainers: input.reuseContainers, ReuseContainers: input.reuseContainers,
Workdir: input.Workdir(), Workdir: input.Workdir(),
BindWorkdir: input.bindWorkdir, BindWorkdir: input.bindWorkdir,
LogOutput: !input.noOutput, LogOutput: !input.noOutput,
JSONLogger: input.jsonLogger, JSONLogger: input.jsonLogger,
Env: envs, Env: envs,
Secrets: secrets, Secrets: secrets,
Token: secrets["GITHUB_TOKEN"], Token: secrets["GITHUB_TOKEN"],
InsecureSecrets: input.insecureSecrets, InsecureSecrets: input.insecureSecrets,
Platforms: input.newPlatforms(), Platforms: input.newPlatforms(),
Privileged: input.privileged, Privileged: input.privileged,
UsernsMode: input.usernsMode, UsernsMode: input.usernsMode,
ContainerArchitecture: input.containerArchitecture, ContainerArchitecture: input.containerArchitecture,
ContainerDaemonSocket: input.containerDaemonSocket, ContainerDaemonSocket: input.containerDaemonSocket,
UseGitIgnore: input.useGitIgnore, UseGitIgnore: input.useGitIgnore,
GitHubInstance: input.githubInstance, GitHubInstance: input.githubInstance,
ContainerCapAdd: input.containerCapAdd, ContainerCapAdd: input.containerCapAdd,
ContainerCapDrop: input.containerCapDrop, ContainerCapDrop: input.containerCapDrop,
AutoRemove: input.autoRemove, AutoRemove: input.autoRemove,
ArtifactServerPath: input.artifactServerPath, ArtifactServerPath: input.artifactServerPath,
ArtifactServerPort: input.artifactServerPort, ArtifactServerPort: input.artifactServerPort,
NoSkipCheckout: input.noSkipCheckout, NoSkipCheckout: input.noSkipCheckout,
RemoteName: input.remoteName, RemoteName: input.remoteName,
ReplaceGheActionWithGithubCom: input.replaceGheActionWithGithubCom,
ReplaceGheActionTokenWithGithubCom: input.replaceGheActionTokenWithGithubCom,
} }
r, err := runner.New(config) r, err := runner.New(config)
if err != nil { if err != nil {

View file

@ -25,35 +25,37 @@ type Runner interface {
// Config contains the config for a new runner // Config contains the config for a new runner
type Config struct { type Config struct {
Actor string // the user that triggered the event Actor string // the user that triggered the event
Workdir string // path to working directory Workdir string // path to working directory
BindWorkdir bool // bind the workdir to the job container BindWorkdir bool // bind the workdir to the job container
EventName string // name of event to run EventName string // name of event to run
EventPath string // path to JSON file to use for event.json in containers EventPath string // path to JSON file to use for event.json in containers
DefaultBranch string // name of the main branch for this repository DefaultBranch string // name of the main branch for this repository
ReuseContainers bool // reuse containers to maintain state ReuseContainers bool // reuse containers to maintain state
ForcePull bool // force pulling of the image, even if already present ForcePull bool // force pulling of the image, even if already present
ForceRebuild bool // force rebuilding local docker image action ForceRebuild bool // force rebuilding local docker image action
LogOutput bool // log the output from docker run LogOutput bool // log the output from docker run
JSONLogger bool // use json or text logger JSONLogger bool // use json or text logger
Env map[string]string // env for containers Env map[string]string // env for containers
Secrets map[string]string // list of secrets Secrets map[string]string // list of secrets
Token string // GitHub token Token string // GitHub token
InsecureSecrets bool // switch hiding output when printing to terminal InsecureSecrets bool // switch hiding output when printing to terminal
Platforms map[string]string // list of platforms Platforms map[string]string // list of platforms
Privileged bool // use privileged mode Privileged bool // use privileged mode
UsernsMode string // user namespace to use UsernsMode string // user namespace to use
ContainerArchitecture string // Desired OS/architecture platform for running containers ContainerArchitecture string // Desired OS/architecture platform for running containers
ContainerDaemonSocket string // Path to Docker daemon socket ContainerDaemonSocket string // Path to Docker daemon socket
UseGitIgnore bool // controls if paths in .gitignore should not be copied into container, default true UseGitIgnore bool // controls if paths in .gitignore should not be copied into container, default true
GitHubInstance string // GitHub instance to use, default "github.com" GitHubInstance string // GitHub instance to use, default "github.com"
ContainerCapAdd []string // list of kernel capabilities to add to the containers ContainerCapAdd []string // list of kernel capabilities to add to the containers
ContainerCapDrop []string // list of kernel capabilities to remove from the containers ContainerCapDrop []string // list of kernel capabilities to remove from the containers
AutoRemove bool // controls if the container is automatically removed upon workflow completion AutoRemove bool // controls if the container is automatically removed upon workflow completion
ArtifactServerPath string // the path where the artifact server stores uploads ArtifactServerPath string // the path where the artifact server stores uploads
ArtifactServerPort string // the port the artifact server binds to ArtifactServerPort string // the port the artifact server binds to
NoSkipCheckout bool // do not skip actions/checkout NoSkipCheckout bool // do not skip actions/checkout
RemoteName string // remote name in local git repo config RemoteName string // remote name in local git repo config
ReplaceGheActionWithGithubCom []string // Use actions from GitHub Enterprise instance to GitHub
ReplaceGheActionTokenWithGithubCom string // Token of private action repo on GitHub.
} }
// Resolves the equivalent host path inside the container // Resolves the equivalent host path inside the container

View file

@ -55,6 +55,14 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
return nil return nil
} }
sar.remoteAction.URL = sar.RunContext.Config.GitHubInstance
for _, action := range sar.RunContext.Config.ReplaceGheActionWithGithubCom {
if strings.EqualFold(fmt.Sprintf("%s/%s", sar.remoteAction.Org, sar.remoteAction.Repo), action) {
sar.remoteAction.URL = "github.com"
github.Token = sar.RunContext.Config.ReplaceGheActionTokenWithGithubCom
}
}
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), strings.ReplaceAll(sar.Step.Uses, "/", "-")) actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), strings.ReplaceAll(sar.Step.Uses, "/", "-"))
gitClone := stepActionRemoteNewCloneExecutor(git.NewGitCloneExecutorInput{ gitClone := stepActionRemoteNewCloneExecutor(git.NewGitCloneExecutorInput{
URL: sar.remoteAction.CloneURL(), URL: sar.remoteAction.CloneURL(),

View file

@ -256,6 +256,147 @@ func TestStepActionRemotePre(t *testing.T) {
} }
} }
func TestStepActionRemotePreThroughAction(t *testing.T) {
table := []struct {
name string
stepModel *model.Step
}{
{
name: "run-pre",
stepModel: &model.Step{
Uses: "org/repo/path@ref",
},
},
}
for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
clonedAction := false
sarm := &stepActionRemoteMocks{}
origStepAtionRemoteNewCloneExecutor := stepActionRemoteNewCloneExecutor
stepActionRemoteNewCloneExecutor = func(input git.NewGitCloneExecutorInput) common.Executor {
return func(ctx context.Context) error {
if input.URL == "https://github.com/org/repo" {
clonedAction = true
}
return nil
}
}
defer (func() {
stepActionRemoteNewCloneExecutor = origStepAtionRemoteNewCloneExecutor
})()
sar := &stepActionRemote{
Step: tt.stepModel,
RunContext: &RunContext{
Config: &Config{
GitHubInstance: "https://enterprise.github.com",
ReplaceGheActionWithGithubCom: []string{"org/repo"},
},
Run: &model.Run{
JobID: "1",
Workflow: &model.Workflow{
Jobs: map[string]*model.Job{
"1": {},
},
},
},
},
readAction: sarm.readAction,
}
suffixMatcher := func(suffix string) interface{} {
return mock.MatchedBy(func(actionDir string) bool {
return strings.HasSuffix(actionDir, suffix)
})
}
sarm.On("readAction", sar.Step, suffixMatcher("org-repo-path@ref"), "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
err := sar.pre()(ctx)
assert.Nil(t, err)
assert.Equal(t, true, clonedAction)
sarm.AssertExpectations(t)
})
}
}
func TestStepActionRemotePreThroughActionToken(t *testing.T) {
table := []struct {
name string
stepModel *model.Step
}{
{
name: "run-pre",
stepModel: &model.Step{
Uses: "org/repo/path@ref",
},
},
}
for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
clonedAction := false
sarm := &stepActionRemoteMocks{}
origStepAtionRemoteNewCloneExecutor := stepActionRemoteNewCloneExecutor
stepActionRemoteNewCloneExecutor = func(input git.NewGitCloneExecutorInput) common.Executor {
return func(ctx context.Context) error {
if input.URL == "https://github.com/org/repo" && input.Token == "PRIVATE_ACTIONS_TOKEN_ON_GITHUB" {
clonedAction = true
}
return nil
}
}
defer (func() {
stepActionRemoteNewCloneExecutor = origStepAtionRemoteNewCloneExecutor
})()
sar := &stepActionRemote{
Step: tt.stepModel,
RunContext: &RunContext{
Config: &Config{
GitHubInstance: "https://enterprise.github.com",
ReplaceGheActionWithGithubCom: []string{"org/repo"},
ReplaceGheActionTokenWithGithubCom: "PRIVATE_ACTIONS_TOKEN_ON_GITHUB",
},
Run: &model.Run{
JobID: "1",
Workflow: &model.Workflow{
Jobs: map[string]*model.Job{
"1": {},
},
},
},
},
readAction: sarm.readAction,
}
suffixMatcher := func(suffix string) interface{} {
return mock.MatchedBy(func(actionDir string) bool {
return strings.HasSuffix(actionDir, suffix)
})
}
sarm.On("readAction", sar.Step, suffixMatcher("org-repo-path@ref"), "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
err := sar.pre()(ctx)
assert.Nil(t, err)
assert.Equal(t, true, clonedAction)
sarm.AssertExpectations(t)
})
}
}
func TestStepActionRemotePost(t *testing.T) { func TestStepActionRemotePost(t *testing.T) {
table := []struct { table := []struct {
name string name string