feature: Add support for github action variables (#1833)

* feature: Add support for github action variables

* add --var flag for github variables

* unitests: Updated unittests to cover vars context.

* Remove syntax extension for vars and correct unit tests

* Update pkg/runner/expression.go

Co-authored-by: ChristopherHX <christopher.homberger@web.de>

---------

Co-authored-by: kuanyong-wong-partior <kuanyong.wong@partior.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
This commit is contained in:
Kuan Yong 2023-06-11 02:09:27 +08:00 committed by GitHub
parent 3ac2b726f2
commit 6ce45e3f24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 38 additions and 0 deletions

View File

@ -16,6 +16,7 @@ type Input struct {
reuseContainers bool reuseContainers bool
bindWorkdir bool bindWorkdir bool
secrets []string secrets []string
vars []string
envs []string envs []string
inputs []string inputs []string
platforms []string platforms []string
@ -26,6 +27,7 @@ type Input struct {
envfile string envfile string
inputfile string inputfile string
secretfile string secretfile string
varfile string
insecureSecrets bool insecureSecrets bool
defaultBranch string defaultBranch string
privileged bool privileged bool
@ -78,6 +80,10 @@ func (i *Input) Secretfile() string {
return i.resolve(i.secretfile) return i.resolve(i.secretfile)
} }
func (i *Input) Varfile() string {
return i.resolve(i.varfile)
}
// Workdir returns path to workdir // Workdir returns path to workdir
func (i *Input) Workdir() string { func (i *Input) Workdir() string {
return i.resolve(".") return i.resolve(".")

View File

@ -49,6 +49,7 @@ func Execute(ctx context.Context, version string) {
rootCmd.Flags().StringVar(&input.remoteName, "remote-name", "origin", "git remote name that will be used to retrieve url of git repo") rootCmd.Flags().StringVar(&input.remoteName, "remote-name", "origin", "git remote name that will be used to retrieve url of git repo")
rootCmd.Flags().StringArrayVarP(&input.secrets, "secret", "s", []string{}, "secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)") rootCmd.Flags().StringArrayVarP(&input.secrets, "secret", "s", []string{}, "secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)")
rootCmd.Flags().StringArrayVar(&input.vars, "var", []string{}, "variable to make available to actions with optional value (e.g. --var myvar=foo or --var myvar)")
rootCmd.Flags().StringArrayVarP(&input.envs, "env", "", []string{}, "env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)") rootCmd.Flags().StringArrayVarP(&input.envs, "env", "", []string{}, "env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)")
rootCmd.Flags().StringArrayVarP(&input.inputs, "input", "", []string{}, "action input to make available to actions (e.g. --input myinput=foo)") rootCmd.Flags().StringArrayVarP(&input.inputs, "input", "", []string{}, "action input to make available to actions (e.g. --input myinput=foo)")
rootCmd.Flags().StringArrayVarP(&input.platforms, "platform", "P", []string{}, "custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)") rootCmd.Flags().StringArrayVarP(&input.platforms, "platform", "P", []string{}, "custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)")
@ -77,6 +78,7 @@ func Execute(ctx context.Context, version string) {
rootCmd.PersistentFlags().BoolVarP(&input.noOutput, "quiet", "q", false, "disable logging of output from steps") rootCmd.PersistentFlags().BoolVarP(&input.noOutput, "quiet", "q", false, "disable logging of output from steps")
rootCmd.PersistentFlags().BoolVarP(&input.dryrun, "dryrun", "n", false, "dryrun mode") rootCmd.PersistentFlags().BoolVarP(&input.dryrun, "dryrun", "n", false, "dryrun mode")
rootCmd.PersistentFlags().StringVarP(&input.secretfile, "secret-file", "", ".secrets", "file with list of secrets to read from (e.g. --secret-file .secrets)") rootCmd.PersistentFlags().StringVarP(&input.secretfile, "secret-file", "", ".secrets", "file with list of secrets to read from (e.g. --secret-file .secrets)")
rootCmd.PersistentFlags().StringVarP(&input.varfile, "var-file", "", ".vars", "file with list of vars to read from (e.g. --var-file .vars)")
rootCmd.PersistentFlags().BoolVarP(&input.insecureSecrets, "insecure-secrets", "", false, "NOT RECOMMENDED! Doesn't hide secrets while printing logs.") rootCmd.PersistentFlags().BoolVarP(&input.insecureSecrets, "insecure-secrets", "", false, "NOT RECOMMENDED! Doesn't hide secrets while printing logs.")
rootCmd.PersistentFlags().StringVarP(&input.envfile, "env-file", "", ".env", "environment file to read and use as env in the containers") rootCmd.PersistentFlags().StringVarP(&input.envfile, "env-file", "", ".env", "environment file to read and use as env in the containers")
rootCmd.PersistentFlags().StringVarP(&input.inputfile, "input-file", "", ".input", "input file to read and use as action input") rootCmd.PersistentFlags().StringVarP(&input.inputfile, "input-file", "", ".input", "input file to read and use as action input")
@ -418,6 +420,10 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
secrets := newSecrets(input.secrets) secrets := newSecrets(input.secrets)
_ = readEnvs(input.Secretfile(), secrets) _ = readEnvs(input.Secretfile(), secrets)
log.Debugf("Loading vars from %s", input.Varfile())
vars := newSecrets(input.vars)
_ = readEnvs(input.Varfile(), vars)
matrixes := parseMatrix(input.matrix) matrixes := parseMatrix(input.matrix)
log.Debugf("Evaluated matrix inclusions: %v", matrixes) log.Debugf("Evaluated matrix inclusions: %v", matrixes)
@ -579,6 +585,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
JSONLogger: input.jsonLogger, JSONLogger: input.jsonLogger,
Env: envs, Env: envs,
Secrets: secrets, Secrets: secrets,
Vars: vars,
Inputs: inputs, Inputs: inputs,
Token: secrets["GITHUB_TOKEN"], Token: secrets["GITHUB_TOKEN"],
InsecureSecrets: input.insecureSecrets, InsecureSecrets: input.insecureSecrets,

View File

@ -19,6 +19,7 @@ type EvaluationEnvironment struct {
Steps map[string]*model.StepResult Steps map[string]*model.StepResult
Runner map[string]interface{} Runner map[string]interface{}
Secrets map[string]string Secrets map[string]string
Vars map[string]string
Strategy map[string]interface{} Strategy map[string]interface{}
Matrix map[string]interface{} Matrix map[string]interface{}
Needs map[string]Needs Needs map[string]Needs
@ -148,6 +149,7 @@ func (impl *interperterImpl) evaluateNode(exprNode actionlint.ExprNode) (interfa
} }
} }
// nolint:gocyclo
func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableNode) (interface{}, error) { func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableNode) (interface{}, error) {
switch strings.ToLower(variableNode.Name) { switch strings.ToLower(variableNode.Name) {
case "github": case "github":
@ -167,6 +169,8 @@ func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableN
return impl.env.Runner, nil return impl.env.Runner, nil
case "secrets": case "secrets":
return impl.env.Secrets, nil return impl.env.Secrets, nil
case "vars":
return impl.env.Vars, nil
case "strategy": case "strategy":
return impl.env.Strategy, nil return impl.env.Strategy, nil
case "matrix": case "matrix":

View File

@ -557,6 +557,7 @@ func TestContexts(t *testing.T) {
// {"contains(steps.*.outputs.name, 'value')", true, "steps-context-array-outputs"}, // {"contains(steps.*.outputs.name, 'value')", true, "steps-context-array-outputs"},
{"runner.os", "Linux", "runner-context"}, {"runner.os", "Linux", "runner-context"},
{"secrets.name", "value", "secrets-context"}, {"secrets.name", "value", "secrets-context"},
{"vars.name", "value", "vars-context"},
{"strategy.fail-fast", true, "strategy-context"}, {"strategy.fail-fast", true, "strategy-context"},
{"matrix.os", "Linux", "matrix-context"}, {"matrix.os", "Linux", "matrix-context"},
{"needs.job-id.outputs.output-name", "value", "needs-context"}, {"needs.job-id.outputs.output-name", "value", "needs-context"},
@ -593,6 +594,9 @@ func TestContexts(t *testing.T) {
Secrets: map[string]string{ Secrets: map[string]string{
"name": "value", "name": "value",
}, },
Vars: map[string]string{
"name": "value",
},
Strategy: map[string]interface{}{ Strategy: map[string]interface{}{
"fail-fast": true, "fail-fast": true,
}, },

View File

@ -77,6 +77,7 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
// but required to interpolate/evaluate the step outputs on the job // but required to interpolate/evaluate the step outputs on the job
Steps: rc.getStepsContext(), Steps: rc.getStepsContext(),
Secrets: getWorkflowSecrets(ctx, rc), Secrets: getWorkflowSecrets(ctx, rc),
Vars: getWorkflowVars(ctx, rc),
Strategy: strategy, Strategy: strategy,
Matrix: rc.Matrix, Matrix: rc.Matrix,
Needs: using, Needs: using,
@ -124,6 +125,7 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
Job: rc.getJobContext(), Job: rc.getJobContext(),
Steps: rc.getStepsContext(), Steps: rc.getStepsContext(),
Secrets: getWorkflowSecrets(ctx, rc), Secrets: getWorkflowSecrets(ctx, rc),
Vars: getWorkflowVars(ctx, rc),
Strategy: strategy, Strategy: strategy,
Matrix: rc.Matrix, Matrix: rc.Matrix,
Needs: using, Needs: using,
@ -483,3 +485,7 @@ func getWorkflowSecrets(ctx context.Context, rc *RunContext) map[string]string {
return rc.Config.Secrets return rc.Config.Secrets
} }
func getWorkflowVars(ctx context.Context, rc *RunContext) map[string]string {
return rc.Config.Vars
}

View File

@ -28,6 +28,9 @@ func createRunContext(t *testing.T) *RunContext {
Secrets: map[string]string{ Secrets: map[string]string{
"CASE_INSENSITIVE_SECRET": "value", "CASE_INSENSITIVE_SECRET": "value",
}, },
Vars: map[string]string{
"CASE_INSENSITIVE_VAR": "value",
},
}, },
Env: map[string]string{ Env: map[string]string{
"key": "value", "key": "value",
@ -122,6 +125,8 @@ func TestEvaluateRunContext(t *testing.T) {
{"env.key", "value", ""}, {"env.key", "value", ""},
{"secrets.CASE_INSENSITIVE_SECRET", "value", ""}, {"secrets.CASE_INSENSITIVE_SECRET", "value", ""},
{"secrets.case_insensitive_secret", "value", ""}, {"secrets.case_insensitive_secret", "value", ""},
{"vars.CASE_INSENSITIVE_VAR", "value", ""},
{"vars.case_insensitive_var", "value", ""},
{"format('{{0}}', 'test')", "{0}", ""}, {"format('{{0}}', 'test')", "{0}", ""},
{"format('{{{0}}}', 'test')", "{test}", ""}, {"format('{{{0}}}', 'test')", "{test}", ""},
{"format('}}')", "}", ""}, {"format('}}')", "}", ""},
@ -195,6 +200,9 @@ func TestInterpolate(t *testing.T) {
Secrets: map[string]string{ Secrets: map[string]string{
"CASE_INSENSITIVE_SECRET": "value", "CASE_INSENSITIVE_SECRET": "value",
}, },
Vars: map[string]string{
"CASE_INSENSITIVE_VAR": "value",
},
}, },
Env: map[string]string{ Env: map[string]string{
"KEYWITHNOTHING": "valuewithnothing", "KEYWITHNOTHING": "valuewithnothing",
@ -229,6 +237,8 @@ func TestInterpolate(t *testing.T) {
{" ${{ env.KEY_WITH_UNDERSCORES }} ", " value_with_underscores "}, {" ${{ env.KEY_WITH_UNDERSCORES }} ", " value_with_underscores "},
{"${{ secrets.CASE_INSENSITIVE_SECRET }}", "value"}, {"${{ secrets.CASE_INSENSITIVE_SECRET }}", "value"},
{"${{ secrets.case_insensitive_secret }}", "value"}, {"${{ secrets.case_insensitive_secret }}", "value"},
{"${{ vars.CASE_INSENSITIVE_VAR }}", "value"},
{"${{ vars.case_insensitive_var }}", "value"},
{"${{ env.UNKNOWN }}", ""}, {"${{ env.UNKNOWN }}", ""},
{"${{ env.SOMETHING_TRUE }}", "true"}, {"${{ env.SOMETHING_TRUE }}", "true"},
{"${{ env.SOMETHING_FALSE }}", "false"}, {"${{ env.SOMETHING_FALSE }}", "false"},

View File

@ -34,6 +34,7 @@ type Config struct {
Env map[string]string // env for containers Env map[string]string // env for containers
Inputs map[string]string // manually passed action inputs Inputs map[string]string // manually passed action inputs
Secrets map[string]string // list of secrets Secrets map[string]string // list of secrets
Vars map[string]string // list of vars
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