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:
Josh Soref 2021-03-30 15:26:25 -04:00 committed by GitHub
parent d67e282f68
commit 5752a03dcd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 96 additions and 23 deletions

View file

@ -7,6 +7,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
with:
go-version: 1.16
- uses: golangci/golangci-lint-action@v2
env:
CGO_ENABLED: 0
@ -24,7 +27,7 @@ jobs:
uses: docker/setup-qemu-action@v1
- uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.16
- run: go test -cover -coverprofile=coverage.txt -covermode=atomic ./...
env:
CGO_ENABLED: 0
@ -42,7 +45,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.16
- name: Install MacOS Docker
uses: docker-practice/actions-setup-docker@master
- run: go test -v -timeout 1h -cover ./...
@ -58,6 +61,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
with:
go-version: 1.16
- name: GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
@ -90,6 +96,9 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-go@v1
with:
go-version: 1.16
- name: GoReleaser
uses: goreleaser/goreleaser-action@v1
with:

View file

@ -1,5 +1,5 @@
name: "Test how expressions are handled on Github"
name: "Test how expressions are handled on GitHub"
on: push
env:

View file

@ -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
env:

View file

@ -1,5 +1,5 @@
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)
MAJOR_VERSION = $(word 1, $(subst ., ,$(VERSION)))
MINOR_VERSION = $(word 2, $(subst ., ,$(VERSION)))

View file

@ -102,7 +102,7 @@ It will save that information to `~/.actrc`, please refer to [Configuration](#co
-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)
--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
-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)

View file

@ -27,7 +27,7 @@ func Execute(ctx context.Context, version string) {
input := new(Input)
var rootCmd = &cobra.Command{
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),
RunE: newRunCommand(ctx, input),
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().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.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().StringVarP(&input.eventPath, "eventpath", "e", "", "path to event JSON file")
rootCmd.Flags().StringVar(&input.defaultBranch, "defaultbranch", "", "the name of the main branch")

2
go.mod
View file

@ -1,6 +1,6 @@
module github.com/nektos/act
go 1.14
go 1.16
require (
github.com/AlecAivazis/survey/v2 v2.2.7

View file

@ -189,6 +189,7 @@ type NewGitCloneExecutorInput struct {
Dir string
}
// CloneIfRequired ...
func CloneIfRequired(refName plumbing.ReferenceName, input NewGitCloneExecutorInput, logger log.FieldLogger) (*git.Repository, error) {
r, err := git.PlainOpen(input.Dir)
if err != nil {

View file

@ -161,7 +161,7 @@ func TestGitFindRef(t *testing.T) {
t.Run(name, func(t *testing.T) {
dir := filepath.Join(basedir, name)
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))
tt.Prepare(t, dir)
ref, err := FindGitRef(dir)

View file

@ -38,6 +38,16 @@ const (
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.
type Action struct {
Name string `yaml:"name"`
@ -45,15 +55,8 @@ type Action struct {
Description string `yaml:"description"`
Inputs map[string]Input `yaml:"inputs"`
Outputs map[string]Output `yaml:"outputs"`
Runs 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"`
} `yaml:"runs"`
Branding struct {
Runs ActionRuns `yaml:"runs"`
Branding struct {
Color string `yaml:"color"`
Icon string `yaml:"icon"`
} `yaml:"branding"`

View file

@ -211,7 +211,7 @@ func updateTestExpressionWorkflow(t *testing.T, tables []struct {
}
workflow := fmt.Sprintf(`
name: "Test how expressions are handled on Github"
name: "Test how expressions are handled on GitHub"
on: push
env:

View 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)

View file

@ -316,7 +316,7 @@ func (rc *RunContext) EvalBool(expr string) (bool, error) {
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.
// Hence env.SOMETHING will be evaluated to true in an if: expression
// regardless if SOMETHING is set to false, true or any other string.

View file

@ -169,7 +169,7 @@ func updateTestIfWorkflow(t *testing.T, tables []struct {
}
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
env:

View file

@ -24,7 +24,7 @@ type Config struct {
EventPath string // path to JSON file to use for event.json in containers
DefaultBranch string // name of the main branch for this repository
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
Env map[string]string // env for containers
Secrets map[string]string // list of secrets

View file

@ -2,7 +2,10 @@ package runner
import (
"context"
// Go told me to?
_ "embed"
"fmt"
"io/ioutil"
"os"
"path"
"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 {
return func(ctx context.Context) error {
f, err := os.Open(filepath.Join(actionDir, actionPath, "action.yml"))
if os.IsNotExist(err) {
f, err = os.Open(filepath.Join(actionDir, actionPath, "action.yaml"))
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
}
} else if err != nil {