* added input flags

* added input as part of the action event and added test cases

* updated readme

Co-authored-by: ChristopherHX <christopher.homberger@web.de>
This commit is contained in:
Shubh Bapna 2023-01-14 00:58:17 +05:30 committed by GitHub
parent b2fb9e64ac
commit 767e6a8696
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 96 additions and 11 deletions

View file

@ -186,6 +186,8 @@ It will save that information to `~/.actrc`, please refer to [Configuration](#co
--github-instance string GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server. (default "github.com") --github-instance string GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server. (default "github.com")
-g, --graph draw workflows -g, --graph draw workflows
-h, --help help for act -h, --help help for act
--input stringArray action input to make available to actions (e.g. --input myinput=foo)
--input-file string input file to read and use as action input (default ".input")
--insecure-secrets NOT RECOMMENDED! Doesn't hide secrets while printing logs. --insecure-secrets NOT RECOMMENDED! Doesn't hide secrets while printing logs.
-j, --job string run job -j, --job string run job
-l, --list list workflows -l, --list list workflows
@ -408,7 +410,7 @@ act pull_request -e pull-request.json
Act will properly provide `github.head_ref` and `github.base_ref` to the action as expected. Act will properly provide `github.head_ref` and `github.base_ref` to the action as expected.
## Pass Inputs to Manually Triggered Workflows # Pass Inputs to Manually Triggered Workflows
Example workflow file Example workflow file
@ -434,6 +436,14 @@ jobs:
echo "Hello ${{ github.event.inputs.NAME }} and ${{ github.event.inputs.SOME_VALUE }}!" echo "Hello ${{ github.event.inputs.NAME }} and ${{ github.event.inputs.SOME_VALUE }}!"
``` ```
## via input or input-file flag
- `act --input NAME=somevalue` - use `somevalue` as the value for `NAME` input.
- `act --input-file my.input` - load input values from `my.input` file.
- input file format is the same as `.env` format
## via JSON
Example JSON payload file conveniently named `payload.json` Example JSON payload file conveniently named `payload.json`
```json ```json

View file

@ -17,12 +17,14 @@ type Input struct {
bindWorkdir bool bindWorkdir bool
secrets []string secrets []string
envs []string envs []string
inputs []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
inputfile string
secretfile string secretfile string
insecureSecrets bool insecureSecrets bool
defaultBranch string defaultBranch string
@ -84,3 +86,8 @@ func (i *Input) WorkflowsPath() string {
func (i *Input) EventPath() string { func (i *Input) EventPath() string {
return i.resolve(i.eventPath) return i.resolve(i.eventPath)
} }
// Inputfile returns the path to the input file
func (i *Input) Inputfile() string {
return i.resolve(i.inputfile)
}

View file

@ -47,6 +47,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().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.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)")
rootCmd.Flags().BoolVarP(&input.reuseContainers, "reuse", "r", false, "don't remove container(s) on successfully completed workflow(s) to maintain state between runs") rootCmd.Flags().BoolVarP(&input.reuseContainers, "reuse", "r", false, "don't remove container(s) on successfully completed workflow(s) to maintain state between runs")
rootCmd.Flags().BoolVarP(&input.bindWorkdir, "bind", "b", false, "bind working directory to container, rather than copy") rootCmd.Flags().BoolVarP(&input.bindWorkdir, "bind", "b", false, "bind working directory to container, rather than copy")
@ -74,6 +75,7 @@ func Execute(ctx context.Context, version string) {
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().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.containerArchitecture, "container-architecture", "", "", "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.") rootCmd.PersistentFlags().StringVarP(&input.containerArchitecture, "container-architecture", "", "", "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.")
rootCmd.PersistentFlags().StringVarP(&input.containerDaemonSocket, "container-daemon-socket", "", "/var/run/docker.sock", "Path to Docker daemon socket which will be mounted to containers") rootCmd.PersistentFlags().StringVarP(&input.containerDaemonSocket, "container-daemon-socket", "", "/var/run/docker.sock", "Path to Docker daemon socket which will be mounted to containers")
rootCmd.PersistentFlags().StringVarP(&input.containerOptions, "container-options", "", "", "Custom docker container options for the job container without an options property in the job definition") rootCmd.PersistentFlags().StringVarP(&input.containerOptions, "container-options", "", "", "Custom docker container options for the job container without an options property in the job definition")
@ -249,6 +251,21 @@ func setupLogging(cmd *cobra.Command, _ []string) {
} }
} }
func parseEnvs(env []string, envs map[string]string) bool {
if env != nil {
for _, envVar := range env {
e := strings.SplitN(envVar, `=`, 2)
if len(e) == 2 {
envs[e[0]] = e[1]
} else {
envs[e[0]] = ""
}
}
return true
}
return false
}
func readEnvs(path string, envs map[string]string) bool { func readEnvs(path string, envs map[string]string) bool {
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
env, err := godotenv.Read(path) env, err := godotenv.Read(path)
@ -285,18 +302,14 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
log.Debugf("Loading environment from %s", input.Envfile()) log.Debugf("Loading environment from %s", input.Envfile())
envs := make(map[string]string) envs := make(map[string]string)
if input.envs != nil { _ = parseEnvs(input.envs, envs)
for _, envVar := range input.envs {
e := strings.SplitN(envVar, `=`, 2)
if len(e) == 2 {
envs[e[0]] = e[1]
} else {
envs[e[0]] = ""
}
}
}
_ = readEnvs(input.Envfile(), envs) _ = readEnvs(input.Envfile(), envs)
log.Debugf("Loading action inputs from %s", input.Inputfile())
inputs := make(map[string]string)
_ = parseEnvs(input.inputs, inputs)
_ = readEnvs(input.Inputfile(), inputs)
log.Debugf("Loading secrets from %s", input.Secretfile()) log.Debugf("Loading secrets from %s", input.Secretfile())
secrets := newSecrets(input.secrets) secrets := newSecrets(input.secrets)
_ = readEnvs(input.Secretfile(), secrets) _ = readEnvs(input.Secretfile(), secrets)
@ -444,6 +457,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,
Inputs: inputs,
Token: secrets["GITHUB_TOKEN"], Token: secrets["GITHUB_TOKEN"],
InsecureSecrets: input.insecureSecrets, InsecureSecrets: input.insecureSecrets,
Platforms: input.newPlatforms(), Platforms: input.newPlatforms(),

View file

@ -2,6 +2,7 @@ package runner
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"os" "os"
@ -31,6 +32,7 @@ type Config struct {
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
Inputs map[string]string // manually passed action inputs
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
@ -81,6 +83,15 @@ func (runner *runnerImpl) configure() (Runner, error) {
return nil, err return nil, err
} }
runner.eventJSON = string(eventJSONBytes) runner.eventJSON = string(eventJSONBytes)
} else if len(runner.config.Inputs) != 0 {
eventMap := map[string]map[string]string{
"inputs": runner.config.Inputs,
}
eventJSON, err := json.Marshal(eventMap)
if err != nil {
return nil, err
}
runner.eventJSON = string(eventJSON)
} }
return runner, nil return runner, nil
} }

View file

@ -94,6 +94,7 @@ func (j *TestJobFileInfo) runTest(ctx context.Context, t *testing.T, cfg *Config
ReuseContainers: false, ReuseContainers: false,
Env: cfg.Env, Env: cfg.Env,
Secrets: cfg.Secrets, Secrets: cfg.Secrets,
Inputs: cfg.Inputs,
GitHubInstance: "github.com", GitHubInstance: "github.com",
ContainerArchitecture: cfg.ContainerArchitecture, ContainerArchitecture: cfg.ContainerArchitecture,
} }
@ -419,6 +420,27 @@ func TestRunEventSecrets(t *testing.T) {
tjfi.runTest(context.Background(), t, &Config{Secrets: secrets, Env: env}) tjfi.runTest(context.Background(), t, &Config{Secrets: secrets, Env: env})
} }
func TestRunActionInputs(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
workflowPath := "input-from-cli"
tjfi := TestJobFileInfo{
workdir: workdir,
workflowPath: workflowPath,
eventName: "workflow_dispatch",
errorMessage: "",
platforms: platforms,
}
inputs := map[string]string{
"SOME_INPUT": "input",
}
tjfi.runTest(context.Background(), t, &Config{Inputs: inputs})
}
func TestRunEventPullRequest(t *testing.T) { func TestRunEventPullRequest(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")

View file

@ -0,0 +1,21 @@
on:
workflow_dispatch:
inputs:
NAME:
description: "A random input name for the workflow"
type: string
required: true
SOME_VALUE:
description: "Some other input to pass"
type: string
required: true
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Test with inputs
run: |
[ -z "${{ github.event.inputs.SOME_INPUT }}" ] && exit 1 || exit 0