Support running commands in repositories without action.yaml (#293)
* Comment for public function * Add git describe fallback * spelling: github * Set initial branch to satisfy tests for modern git * Clarify -even- if * Go 1.16 * Support running commands in repositories without action.yaml Support runnings commands with only a Docker file Co-authored-by: Casey Lee <cplee@nektos.com>
This commit is contained in:
parent
d67e282f68
commit
5752a03dcd
16 changed files with 96 additions and 23 deletions
13
.github/workflows/push.yml
vendored
13
.github/workflows/push.yml
vendored
|
@ -7,6 +7,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.16
|
||||||
- uses: golangci/golangci-lint-action@v2
|
- uses: golangci/golangci-lint-action@v2
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
@ -24,7 +27,7 @@ jobs:
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- uses: actions/setup-go@v1
|
- uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
go-version: 1.14
|
go-version: 1.16
|
||||||
- run: go test -cover -coverprofile=coverage.txt -covermode=atomic ./...
|
- run: go test -cover -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
@ -42,7 +45,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-go@v1
|
- uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
go-version: 1.14
|
go-version: 1.16
|
||||||
- name: Install MacOS Docker
|
- name: Install MacOS Docker
|
||||||
uses: docker-practice/actions-setup-docker@master
|
uses: docker-practice/actions-setup-docker@master
|
||||||
- run: go test -v -timeout 1h -cover ./...
|
- run: go test -v -timeout 1h -cover ./...
|
||||||
|
@ -58,6 +61,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.16
|
||||||
- name: GoReleaser
|
- name: GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
|
@ -90,6 +96,9 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.16
|
||||||
- name: GoReleaser
|
- name: GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v1
|
uses: goreleaser/goreleaser-action@v1
|
||||||
with:
|
with:
|
||||||
|
|
2
.github/workflows/test-expressions.yml
vendored
2
.github/workflows/test-expressions.yml
vendored
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
name: "Test how expressions are handled on Github"
|
name: "Test how expressions are handled on GitHub"
|
||||||
on: push
|
on: push
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
2
.github/workflows/test-if.yml
vendored
2
.github/workflows/test-if.yml
vendored
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
name: "Test what expressions result in true and false on Github"
|
name: "Test what expressions result in true and false on GitHub"
|
||||||
on: push
|
on: push
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -1,5 +1,5 @@
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
VERSION ?= $(shell git describe --tags --dirty | cut -c 2-)
|
VERSION ?= $(shell git describe --tags --dirty --always | cut -c 2-)
|
||||||
IS_SNAPSHOT = $(if $(findstring -, $(VERSION)),true,false)
|
IS_SNAPSHOT = $(if $(findstring -, $(VERSION)),true,false)
|
||||||
MAJOR_VERSION = $(word 1, $(subst ., ,$(VERSION)))
|
MAJOR_VERSION = $(word 1, $(subst ., ,$(VERSION)))
|
||||||
MINOR_VERSION = $(word 2, $(subst ., ,$(VERSION)))
|
MINOR_VERSION = $(word 2, $(subst ., ,$(VERSION)))
|
||||||
|
|
|
@ -102,7 +102,7 @@ It will save that information to `~/.actrc`, please refer to [Configuration](#co
|
||||||
-l, --list list workflows
|
-l, --list list workflows
|
||||||
-P, --platform stringArray custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)
|
-P, --platform stringArray custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)
|
||||||
--privileged use privileged mode
|
--privileged use privileged mode
|
||||||
-p, --pull pull docker image(s) if already present
|
-p, --pull pull docker image(s) even if already present
|
||||||
-q, --quiet disable logging of output from steps
|
-q, --quiet disable logging of output from steps
|
||||||
-r, --reuse reuse action containers to maintain state
|
-r, --reuse reuse action containers to maintain state
|
||||||
-s, --secret stringArray secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)
|
-s, --secret stringArray secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)
|
||||||
|
|
|
@ -27,7 +27,7 @@ func Execute(ctx context.Context, version string) {
|
||||||
input := new(Input)
|
input := new(Input)
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "act [event name to run]\nIf no event name passed, will default to \"on: push\"",
|
Use: "act [event name to run]\nIf no event name passed, will default to \"on: push\"",
|
||||||
Short: "Run Github actions locally by specifying the event name (e.g. `push`) or an action name directly.",
|
Short: "Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.",
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: newRunCommand(ctx, input),
|
RunE: newRunCommand(ctx, input),
|
||||||
PersistentPreRun: setupLogging,
|
PersistentPreRun: setupLogging,
|
||||||
|
@ -43,7 +43,7 @@ func Execute(ctx context.Context, version string) {
|
||||||
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, "reuse action containers to maintain state")
|
rootCmd.Flags().BoolVarP(&input.reuseContainers, "reuse", "r", false, "reuse action containers to maintain state")
|
||||||
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")
|
||||||
rootCmd.Flags().BoolVarP(&input.forcePull, "pull", "p", false, "pull docker image(s) if already present")
|
rootCmd.Flags().BoolVarP(&input.forcePull, "pull", "p", false, "pull docker image(s) even if already present")
|
||||||
rootCmd.Flags().BoolVarP(&input.autodetectEvent, "detect-event", "", false, "Use first event type from workflow as event that triggered the workflow")
|
rootCmd.Flags().BoolVarP(&input.autodetectEvent, "detect-event", "", false, "Use first event type from workflow as event that triggered the workflow")
|
||||||
rootCmd.Flags().StringVarP(&input.eventPath, "eventpath", "e", "", "path to event JSON file")
|
rootCmd.Flags().StringVarP(&input.eventPath, "eventpath", "e", "", "path to event JSON file")
|
||||||
rootCmd.Flags().StringVar(&input.defaultBranch, "defaultbranch", "", "the name of the main branch")
|
rootCmd.Flags().StringVar(&input.defaultBranch, "defaultbranch", "", "the name of the main branch")
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module github.com/nektos/act
|
module github.com/nektos/act
|
||||||
|
|
||||||
go 1.14
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.7
|
github.com/AlecAivazis/survey/v2 v2.2.7
|
||||||
|
|
|
@ -189,6 +189,7 @@ type NewGitCloneExecutorInput struct {
|
||||||
Dir string
|
Dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CloneIfRequired ...
|
||||||
func CloneIfRequired(refName plumbing.ReferenceName, input NewGitCloneExecutorInput, logger log.FieldLogger) (*git.Repository, error) {
|
func CloneIfRequired(refName plumbing.ReferenceName, input NewGitCloneExecutorInput, logger log.FieldLogger) (*git.Repository, error) {
|
||||||
r, err := git.PlainOpen(input.Dir)
|
r, err := git.PlainOpen(input.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -161,7 +161,7 @@ func TestGitFindRef(t *testing.T) {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
dir := filepath.Join(basedir, name)
|
dir := filepath.Join(basedir, name)
|
||||||
require.NoError(t, os.MkdirAll(dir, 0755))
|
require.NoError(t, os.MkdirAll(dir, 0755))
|
||||||
require.NoError(t, gitCmd("-C", dir, "init"))
|
require.NoError(t, gitCmd("-C", dir, "init", "--initial-branch=master"))
|
||||||
require.NoError(t, cleanGitHooks(dir))
|
require.NoError(t, cleanGitHooks(dir))
|
||||||
tt.Prepare(t, dir)
|
tt.Prepare(t, dir)
|
||||||
ref, err := FindGitRef(dir)
|
ref, err := FindGitRef(dir)
|
||||||
|
|
|
@ -38,6 +38,16 @@ const (
|
||||||
ActionRunsUsingDocker = "docker"
|
ActionRunsUsingDocker = "docker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ActionRuns are a field in Action
|
||||||
|
type ActionRuns struct {
|
||||||
|
Using ActionRunsUsing `yaml:"using"`
|
||||||
|
Env map[string]string `yaml:"env"`
|
||||||
|
Main string `yaml:"main"`
|
||||||
|
Image string `yaml:"image"`
|
||||||
|
Entrypoint []string `yaml:"entrypoint"`
|
||||||
|
Args []string `yaml:"args"`
|
||||||
|
}
|
||||||
|
|
||||||
// Action describes a metadata file for GitHub actions. The metadata filename must be either action.yml or action.yaml. The data in the metadata file defines the inputs, outputs and main entrypoint for your action.
|
// Action describes a metadata file for GitHub actions. The metadata filename must be either action.yml or action.yaml. The data in the metadata file defines the inputs, outputs and main entrypoint for your action.
|
||||||
type Action struct {
|
type Action struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
|
@ -45,15 +55,8 @@ type Action struct {
|
||||||
Description string `yaml:"description"`
|
Description string `yaml:"description"`
|
||||||
Inputs map[string]Input `yaml:"inputs"`
|
Inputs map[string]Input `yaml:"inputs"`
|
||||||
Outputs map[string]Output `yaml:"outputs"`
|
Outputs map[string]Output `yaml:"outputs"`
|
||||||
Runs struct {
|
Runs ActionRuns `yaml:"runs"`
|
||||||
Using ActionRunsUsing `yaml:"using"`
|
Branding struct {
|
||||||
Env map[string]string `yaml:"env"`
|
|
||||||
Main string `yaml:"main"`
|
|
||||||
Image string `yaml:"image"`
|
|
||||||
Entrypoint []string `yaml:"entrypoint"`
|
|
||||||
Args []string `yaml:"args"`
|
|
||||||
} `yaml:"runs"`
|
|
||||||
Branding struct {
|
|
||||||
Color string `yaml:"color"`
|
Color string `yaml:"color"`
|
||||||
Icon string `yaml:"icon"`
|
Icon string `yaml:"icon"`
|
||||||
} `yaml:"branding"`
|
} `yaml:"branding"`
|
||||||
|
|
|
@ -211,7 +211,7 @@ func updateTestExpressionWorkflow(t *testing.T, tables []struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
workflow := fmt.Sprintf(`
|
workflow := fmt.Sprintf(`
|
||||||
name: "Test how expressions are handled on Github"
|
name: "Test how expressions are handled on GitHub"
|
||||||
on: push
|
on: push
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
14
pkg/runner/res/trampoline.js
Normal file
14
pkg/runner/res/trampoline.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
const { spawnSync } = require('child_process')
|
||||||
|
const spawnArguments={
|
||||||
|
cwd: process.env['INPUT_CWD'],
|
||||||
|
stdio: [
|
||||||
|
process.stdin,
|
||||||
|
process.stdout,
|
||||||
|
process.stderr,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const child=spawnSync(
|
||||||
|
'/bin/sh',
|
||||||
|
[ '-c' ].concat(process.env['INPUT_COMMAND']),
|
||||||
|
spawnArguments)
|
||||||
|
process.exit(child.status)
|
|
@ -316,7 +316,7 @@ func (rc *RunContext) EvalBool(expr string) (bool, error) {
|
||||||
|
|
||||||
interpolatedPart, isString := rc.ExprEval.InterpolateWithStringCheck(part)
|
interpolatedPart, isString := rc.ExprEval.InterpolateWithStringCheck(part)
|
||||||
|
|
||||||
// This peculiar transformation has to be done because the Github parser
|
// This peculiar transformation has to be done because the GitHub parser
|
||||||
// treats false returned from contexts as a string, not a boolean.
|
// treats false returned from contexts as a string, not a boolean.
|
||||||
// Hence env.SOMETHING will be evaluated to true in an if: expression
|
// Hence env.SOMETHING will be evaluated to true in an if: expression
|
||||||
// regardless if SOMETHING is set to false, true or any other string.
|
// regardless if SOMETHING is set to false, true or any other string.
|
||||||
|
|
|
@ -169,7 +169,7 @@ func updateTestIfWorkflow(t *testing.T, tables []struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
workflow := fmt.Sprintf(`
|
workflow := fmt.Sprintf(`
|
||||||
name: "Test what expressions result in true and false on Github"
|
name: "Test what expressions result in true and false on GitHub"
|
||||||
on: push
|
on: push
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -24,7 +24,7 @@ type Config struct {
|
||||||
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, if already present
|
ForcePull bool // force pulling of the image, even if already present
|
||||||
LogOutput bool // log the output from docker run
|
LogOutput bool // log the output from docker run
|
||||||
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
|
||||||
|
|
|
@ -2,7 +2,10 @@ package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
// Go told me to?
|
||||||
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -296,12 +299,55 @@ func (sc *StepContext) runUsesContainer() common.Executor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed res/trampoline.js
|
||||||
|
var trampoline []byte
|
||||||
|
|
||||||
func (sc *StepContext) setupAction(actionDir string, actionPath string) common.Executor {
|
func (sc *StepContext) setupAction(actionDir string, actionPath string) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
f, err := os.Open(filepath.Join(actionDir, actionPath, "action.yml"))
|
f, err := os.Open(filepath.Join(actionDir, actionPath, "action.yml"))
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
f, err = os.Open(filepath.Join(actionDir, actionPath, "action.yaml"))
|
f, err = os.Open(filepath.Join(actionDir, actionPath, "action.yaml"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, err2 := os.Stat(filepath.Join(actionDir, actionPath, "Dockerfile")); err2 == nil {
|
||||||
|
sc.Action = &model.Action{
|
||||||
|
Name: "(Synthetic)",
|
||||||
|
Runs: model.ActionRuns{
|
||||||
|
Using: "docker",
|
||||||
|
Image: "Dockerfile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
log.Debugf("Using synthetic action %v for Dockerfile", sc.Action)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if sc.Step.With != nil {
|
||||||
|
if val, ok := sc.Step.With["args"]; ok {
|
||||||
|
err2 := ioutil.WriteFile(filepath.Join(actionDir, actionPath, "trampoline.js"), trampoline, 0400)
|
||||||
|
if err2 != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sc.Action = &model.Action{
|
||||||
|
Name: "(Synthetic)",
|
||||||
|
Inputs: map[string]model.Input{
|
||||||
|
"cwd": {
|
||||||
|
Description: "(Actual working directory)",
|
||||||
|
Required: false,
|
||||||
|
Default: filepath.Join(actionDir, actionPath),
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
Description: "(Actual program)",
|
||||||
|
Required: false,
|
||||||
|
Default: val,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Runs: model.ActionRuns{
|
||||||
|
Using: "node12",
|
||||||
|
Main: "trampoline.js",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
log.Debugf("Using synthetic action %v", sc.Action)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue