Skip to content

Commit

Permalink
local actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Unit Test committed Feb 10, 2020
1 parent d3ef8ac commit 8c41826
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 137 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/basic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ jobs:
- run: cp $GITHUB_EVENT_PATH $HOME/foo.json
- run: ls $HOME
- run: cat $HOME/foo.json
- uses: docker://alpine:3.8
with:
args: uname -a
local-action:
runs-on: ubuntu-latest
steps:
- uses: ./.github/workflows/docker-url

16 changes: 16 additions & 0 deletions .github/workflows/docker-url/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: docker-url
author: nektos
description: testing
inputs:
who-to-greet:
description: who to greet
required: true
default: World
runs:
using: docker
image: docker://alpine:3.8
env:
TEST: enabled
args:
- echo
- ${INPUT_WHO_TO_GREET}
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BU
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb h1:PyjxRdW1mqCmSoxy/6uP01P7CGbsD+woX+oOWbaUPwQ=
github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
github.com/docker/engine v1.13.1 h1:Cks33UT9YBW5Xyc3MtGDq2IPgqfJtJ+qkFaxc2b0Euc=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
Expand Down
57 changes: 57 additions & 0 deletions pkg/model/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package model

import (
"io"

"gopkg.in/yaml.v2"
)

// ActionRunsUsing is the type of runner for the action
type ActionRunsUsing string

const (
// ActionRunsUsingNode12 for running with node12
ActionRunsUsingNode12 = "node12"
// ActionRunsUsingDocker for running with docker
ActionRunsUsingDocker = "docker"
)

// 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"`
Author string `yaml:"author"`
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 {
Color string `yaml:"color"`
Icon string `yaml:"icon"`
} `yaml:"branding"`
}

// Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables. Input ids with uppercase letters are converted to lowercase during runtime. We recommended using lowercase input ids.
type Input struct {
Description string `yaml:"description"`
Required bool `yaml:"required"`
Default string `yaml:"default"`
}

// Output parameters allow you to declare data that an action sets. Actions that run later in a workflow can use the output data set in previously run actions. For example, if you had an action that performed the addition of two inputs (x + y = z), the action could output the sum (z) for other actions to use as an input.
type Output struct {
Description string `yaml:"description"`
}

// ReadAction reads an action from a reader
func ReadAction(in io.Reader) (*Action, error) {
a := new(Action)
err := yaml.NewDecoder(in).Decode(a)
return a, err
}
31 changes: 31 additions & 0 deletions pkg/model/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package model
import (
"fmt"
"io"
"regexp"
"strings"

"gopkg.in/yaml.v2"
Expand Down Expand Up @@ -63,11 +64,41 @@ func (s *Step) GetEnv() map[string]string {
}
for k, v := range s.With {
envKey := fmt.Sprintf("INPUT_%s", strings.ToUpper(k))
envKey = regexp.MustCompile("[^A-Z0-9]").ReplaceAllString(envKey, "_")
rtnEnv[envKey] = v
}
return rtnEnv
}

// StepType describes what type of step we are about to run
type StepType int

const (
// StepTypeRun is all steps that have a `run` attribute
StepTypeRun StepType = iota

//StepTypeUsesDockerURL is all steps that have a `uses` that is of the form `docker://...`
StepTypeUsesDockerURL

//StepTypeUsesActionLocal is all steps that have a `uses` that is a reference to a github repo
StepTypeUsesActionLocal

//StepTypeUsesActionRemote is all steps that have a `uses` that is a local action in a subdirectory
StepTypeUsesActionRemote
)

// Type returns the type of the step
func (s *Step) Type() StepType {
if s.Run != "" {
return StepTypeRun
} else if strings.HasPrefix(s.Uses, "docker://") {
return StepTypeUsesDockerURL
} else if strings.HasPrefix(s.Uses, "./") {
return StepTypeUsesActionLocal
}
return StepTypeUsesActionRemote
}

// ReadWorkflow returns a list of jobs for a given workflow file reader
func ReadWorkflow(in io.Reader) (*Workflow, error) {
w := new(Workflow)
Expand Down
139 changes: 2 additions & 137 deletions pkg/runner/run_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,48 +38,6 @@ func (rc *RunContext) GetEnv() map[string]string {
return rc.Env
}

// StepEnv returns the env for a step
func (rc *RunContext) StepEnv(step *model.Step) map[string]string {
env := make(map[string]string)
env["HOME"] = "/github/home"
env["GITHUB_WORKFLOW"] = rc.Run.Workflow.Name
env["GITHUB_RUN_ID"] = "1"
env["GITHUB_RUN_NUMBER"] = "1"
env["GITHUB_ACTION"] = step.ID
env["GITHUB_ACTOR"] = "nektos/act"

repoPath := rc.Config.Workdir
repo, err := common.FindGithubRepo(repoPath)
if err != nil {
log.Warningf("unable to get git repo: %v", err)
} else {
env["GITHUB_REPOSITORY"] = repo
}
env["GITHUB_EVENT_NAME"] = rc.Config.EventName
env["GITHUB_EVENT_PATH"] = "/github/workflow/event.json"
env["GITHUB_WORKSPACE"] = "/github/workspace"

_, rev, err := common.FindGitRevision(repoPath)
if err != nil {
log.Warningf("unable to get git revision: %v", err)
} else {
env["GITHUB_SHA"] = rev
}

ref, err := common.FindGitRef(repoPath)
if err != nil {
log.Warningf("unable to get git ref: %v", err)
} else {
log.Infof("using github ref: %s", ref)
env["GITHUB_REF"] = ref
}
job := rc.Run.Job()
if job.Container != nil {
return mergeMaps(rc.GetEnv(), job.Container.Env, step.GetEnv(), env)
}
return mergeMaps(rc.GetEnv(), step.GetEnv(), env)
}

// Close cleans up temp dir
func (rc *RunContext) Close(ctx context.Context) error {
return os.RemoveAll(rc.Tempdir)
Expand All @@ -91,105 +49,11 @@ func (rc *RunContext) Executor() common.Executor {
steps = append(steps, rc.setupTempDir())

for _, step := range rc.Run.Job().Steps {
containerSpec := new(model.ContainerSpec)

var stepExecutor common.Executor
if step.Run != "" {
stepExecutor = common.NewPipelineExecutor(
rc.setupContainerSpec(step, containerSpec),
rc.pullImage(containerSpec),
rc.runContainer(containerSpec),
)
} else if step.Uses != "" {
stepExecutor = common.NewErrorExecutor(fmt.Errorf("Not yet implemented - job:%s step:%+v", rc.Run, step))
// clone action repo
// read action.yaml
// if runs.using == node12, start node12 container and run `main`
// if runs.using == docker, pull `image` and run
// caputre output/commands
} else {
stepExecutor = common.NewErrorExecutor(fmt.Errorf("Unable to determine how to run job:%s step:%+v", rc.Run, step))
}
steps = append(steps, stepExecutor)
steps = append(steps, rc.newStepExecutor(step))
}
return common.NewPipelineExecutor(steps...).Finally(rc.Close)
}

func (rc *RunContext) setupContainerSpec(step *model.Step, containerSpec *model.ContainerSpec) common.Executor {
return func(ctx context.Context) error {
job := rc.Run.Job()

containerSpec.Env = rc.StepEnv(step)

if step.Uses != "" {
containerSpec.Image = step.Uses
} else if job.Container != nil {
containerSpec.Image = job.Container.Image
containerSpec.Args = rc.shellCommand(step.Shell, step.Run)
containerSpec.Ports = job.Container.Ports
containerSpec.Volumes = job.Container.Volumes
containerSpec.Options = job.Container.Options
} else if step.Run != "" {
containerSpec.Image = platformImage(job.RunsOn)
containerSpec.Args = rc.shellCommand(step.Shell, step.Run)
} else {
return fmt.Errorf("Unable to setup container for %s", step)
}
return nil
}
}

func (rc *RunContext) shellCommand(shell string, run string) string {
shellCommand := ""

switch shell {
case "", "bash":
shellCommand = "bash --noprofile --norc -eo pipefail {0}"
case "pwsh":
shellCommand = "pwsh -command \"& '{0}'\""
case "python":
shellCommand = "python {0}"
case "sh":
shellCommand = "sh -e -c {0}"
case "cmd":
shellCommand = "%ComSpec% /D /E:ON /V:OFF /S /C \"CALL \"{0}\"\""
case "powershell":
shellCommand = "powershell -command \"& '{0}'\""
default:
shellCommand = shell
}

tempScript, err := ioutil.TempFile(rc.Tempdir, ".temp-script-")
if err != nil {
log.Fatalf("Unable to create temp script %v", err)
}

if _, err := tempScript.Write([]byte(run)); err != nil {
log.Fatal(err)
}
log.Debugf("Wrote command '%s' to '%s'", run, tempScript.Name())
if err := tempScript.Close(); err != nil {
log.Fatal(err)
}
containerPath := fmt.Sprintf("/github/home/%s", filepath.Base(tempScript.Name()))
cmd := strings.Replace(shellCommand, "{0}", containerPath, 1)
log.Debugf("about to run %s", cmd)
return cmd
}

func platformImage(platform string) string {
switch platform {
case "ubuntu-latest", "ubuntu-18.04":
return "ubuntu:18.04"
case "ubuntu-16.04":
return "ubuntu:16.04"
case "windows-latest", "windows-2019", "macos-latest", "macos-10.15":
return ""
default:
return ""
}
}

func mergeMaps(maps ...map[string]string) map[string]string {
rtnMap := make(map[string]string)
for _, m := range maps {
Expand All @@ -208,6 +72,7 @@ func (rc *RunContext) setupTempDir() common.Executor {
tempBase = "/tmp"
}
rc.Tempdir, err = ioutil.TempDir(tempBase, "act-")
log.Debugf("Setup tempdir %s", rc.Tempdir)
return err
}
}
Expand Down
Loading

0 comments on commit 8c41826

Please sign in to comment.