Skip to content

Commit

Permalink
Input (nektos#1524)
Browse files Browse the repository at this point in the history
* added input flags

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

* updated readme

Co-authored-by: ChristopherHX <[email protected]>
  • Loading branch information
shubhbapna and ChristopherHX authored Jan 13, 2023
1 parent b2fb9e6 commit 767e6a8
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 11 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
-g, --graph draw workflows
-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.
-j, --job string run job
-l, --list list workflows
Expand Down Expand Up @@ -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.

## Pass Inputs to Manually Triggered Workflows
# Pass Inputs to Manually Triggered Workflows

Example workflow file

Expand All @@ -434,6 +436,14 @@ jobs:
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`

```json
Expand Down
7 changes: 7 additions & 0 deletions cmd/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ type Input struct {
bindWorkdir bool
secrets []string
envs []string
inputs []string
platforms []string
dryrun bool
forcePull bool
forceRebuild bool
noOutput bool
envfile string
inputfile string
secretfile string
insecureSecrets bool
defaultBranch string
Expand Down Expand Up @@ -84,3 +86,8 @@ func (i *Input) WorkflowsPath() string {
func (i *Input) EventPath() string {
return i.resolve(i.eventPath)
}

// Inputfile returns the path to the input file
func (i *Input) Inputfile() string {
return i.resolve(i.inputfile)
}
34 changes: 24 additions & 10 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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().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.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().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")
Expand Down Expand Up @@ -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().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.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.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")
Expand Down Expand Up @@ -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 {
if _, err := os.Stat(path); err == nil {
env, err := godotenv.Read(path)
Expand Down Expand Up @@ -285,18 +302,14 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str

log.Debugf("Loading environment from %s", input.Envfile())
envs := make(map[string]string)
if input.envs != nil {
for _, envVar := range input.envs {
e := strings.SplitN(envVar, `=`, 2)
if len(e) == 2 {
envs[e[0]] = e[1]
} else {
envs[e[0]] = ""
}
}
}
_ = parseEnvs(input.envs, 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())
secrets := newSecrets(input.secrets)
_ = readEnvs(input.Secretfile(), secrets)
Expand Down Expand Up @@ -444,6 +457,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
JSONLogger: input.jsonLogger,
Env: envs,
Secrets: secrets,
Inputs: inputs,
Token: secrets["GITHUB_TOKEN"],
InsecureSecrets: input.insecureSecrets,
Platforms: input.newPlatforms(),
Expand Down
11 changes: 11 additions & 0 deletions pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package runner

import (
"context"
"encoding/json"
"fmt"
"os"

Expand Down Expand Up @@ -31,6 +32,7 @@ type Config struct {
LogOutput bool // log the output from docker run
JSONLogger bool // use json or text logger
Env map[string]string // env for containers
Inputs map[string]string // manually passed action inputs
Secrets map[string]string // list of secrets
Token string // GitHub token
InsecureSecrets bool // switch hiding output when printing to terminal
Expand Down Expand Up @@ -81,6 +83,15 @@ func (runner *runnerImpl) configure() (Runner, error) {
return nil, err
}
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
}
Expand Down
22 changes: 22 additions & 0 deletions pkg/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func (j *TestJobFileInfo) runTest(ctx context.Context, t *testing.T, cfg *Config
ReuseContainers: false,
Env: cfg.Env,
Secrets: cfg.Secrets,
Inputs: cfg.Inputs,
GitHubInstance: "github.com",
ContainerArchitecture: cfg.ContainerArchitecture,
}
Expand Down Expand Up @@ -419,6 +420,27 @@ func TestRunEventSecrets(t *testing.T) {
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) {
if testing.Short() {
t.Skip("skipping integration test")
Expand Down
21 changes: 21 additions & 0 deletions pkg/runner/testdata/input-from-cli/input.yml
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 767e6a8

Please sign in to comment.