2019-01-12 22:45:25 -06:00
package cmd
import (
2020-02-25 00:11:33 -06:00
"bufio"
2019-01-12 22:45:25 -06:00
"context"
2022-03-22 14:26:10 -05:00
"fmt"
2019-01-12 22:45:25 -06:00
"os"
2019-02-09 20:39:09 -06:00
"path/filepath"
2020-03-02 10:11:46 -06:00
"regexp"
2021-06-23 13:09:27 -05:00
"runtime"
2022-05-17 14:02:15 -05:00
"runtime/debug"
2020-02-25 00:11:33 -06:00
"strings"
2019-01-12 22:45:25 -06:00
2021-01-19 08:30:17 -06:00
"github.com/AlecAivazis/survey/v2"
2023-03-03 08:39:02 -06:00
"github.com/adrg/xdg"
2021-01-12 00:39:43 -06:00
"github.com/andreaskoch/go-fswatch"
2020-03-06 14:30:24 -06:00
"github.com/joho/godotenv"
2019-02-09 20:39:09 -06:00
gitignore "github.com/sabhiram/go-gitignore"
2019-01-12 22:45:25 -06:00
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
2023-04-18 23:19:40 -05:00
"gopkg.in/yaml.v3"
2021-01-19 08:30:17 -06:00
2023-04-28 10:57:40 -05:00
"github.com/nektos/act/pkg/artifactcache"
2021-11-10 11:57:22 -06:00
"github.com/nektos/act/pkg/artifacts"
2021-06-23 13:09:27 -05:00
"github.com/nektos/act/pkg/common"
2022-03-22 14:26:10 -05:00
"github.com/nektos/act/pkg/container"
2021-01-19 08:30:17 -06:00
"github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/runner"
2019-01-12 22:45:25 -06:00
)
// Execute is the entry point to running the CLI
func Execute ( ctx context . Context , version string ) {
2020-02-04 18:38:41 -06:00
input := new ( Input )
2019-01-12 22:45:25 -06:00
var rootCmd = & cobra . Command {
2023-01-15 04:30:41 -06:00
Use : "act [event name to run] [flags]\n\nIf no event name passed, will default to \"on: push\"\nIf actions handles only one event it will be used as default instead of \"on: push\"" ,
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 : setup ( input ) ,
PersistentPostRun : cleanup ( input ) ,
Version : version ,
SilenceUsage : true ,
2019-01-12 22:45:25 -06:00
}
2019-02-09 20:39:09 -06:00
rootCmd . Flags ( ) . BoolP ( "watch" , "w" , false , "watch the contents of the local repo and run when files change" )
2020-02-04 18:38:41 -06:00
rootCmd . Flags ( ) . BoolP ( "list" , "l" , false , "list workflows" )
2020-10-12 12:26:22 -05:00
rootCmd . Flags ( ) . BoolP ( "graph" , "g" , false , "draw workflows" )
2022-08-31 13:41:56 -05:00
rootCmd . Flags ( ) . StringP ( "job" , "j" , "" , "run a specific job ID" )
2022-03-22 14:26:10 -05:00
rootCmd . Flags ( ) . BoolP ( "bug-report" , "" , false , "Display system information for bug report" )
2022-04-04 12:53:08 -05:00
rootCmd . Flags ( ) . StringVar ( & input . remoteName , "remote-name" , "origin" , "git remote name that will be used to retrieve url of git repo" )
2020-02-17 23:51:49 -06:00
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)" )
2023-06-10 13:09:27 -05:00
rootCmd . Flags ( ) . StringArrayVar ( & input . vars , "var" , [ ] string { } , "variable to make available to actions with optional value (e.g. --var myvar=foo or --var myvar)" )
2021-06-10 18:12:05 -05:00
rootCmd . Flags ( ) . StringArrayVarP ( & input . envs , "env" , "" , [ ] string { } , "env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)" )
2023-01-13 13:28:17 -06:00
rootCmd . Flags ( ) . StringArrayVarP ( & input . inputs , "input" , "" , [ ] string { } , "action input to make available to actions (e.g. --input myinput=foo)" )
2020-02-19 21:16:40 -06:00
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)" )
2021-12-15 23:11:56 -06:00
rootCmd . Flags ( ) . BoolVarP ( & input . reuseContainers , "reuse" , "r" , false , "don't remove container(s) on successfully completed workflow(s) to maintain state between runs" )
2020-02-24 19:48:21 -06:00
rootCmd . Flags ( ) . BoolVarP ( & input . bindWorkdir , "bind" , "b" , false , "bind working directory to container, rather than copy" )
2023-01-20 09:46:43 -06:00
rootCmd . Flags ( ) . BoolVarP ( & input . forcePull , "pull" , "p" , true , "pull docker image(s) even if already present" )
rootCmd . Flags ( ) . BoolVarP ( & input . forceRebuild , "rebuild" , "" , true , "rebuild local action docker image(s) even if already present" )
2021-01-18 13:42:55 -06:00
rootCmd . Flags ( ) . BoolVarP ( & input . autodetectEvent , "detect-event" , "" , false , "Use first event type from workflow as event that triggered the workflow" )
2020-02-17 23:51:49 -06:00
rootCmd . Flags ( ) . StringVarP ( & input . eventPath , "eventpath" , "e" , "" , "path to event JSON file" )
2020-09-02 09:56:44 -05:00
rootCmd . Flags ( ) . StringVar ( & input . defaultBranch , "defaultbranch" , "" , "the name of the main branch" )
2020-08-01 15:21:49 -05:00
rootCmd . Flags ( ) . BoolVar ( & input . privileged , "privileged" , false , "use privileged mode" )
2021-02-27 10:31:25 -06:00
rootCmd . Flags ( ) . StringVar ( & input . usernsMode , "userns" , "" , "user namespace to use" )
2021-05-03 09:37:20 -05:00
rootCmd . Flags ( ) . BoolVar ( & input . useGitIgnore , "use-gitignore" , true , "Controls whether paths specified in .gitignore should be copied into container" )
2021-06-04 11:06:59 -05:00
rootCmd . Flags ( ) . StringArrayVarP ( & input . containerCapAdd , "container-cap-add" , "" , [ ] string { } , "kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)" )
rootCmd . Flags ( ) . StringArrayVarP ( & input . containerCapDrop , "container-cap-drop" , "" , [ ] string { } , "kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)" )
2021-12-15 23:11:56 -06:00
rootCmd . Flags ( ) . BoolVar ( & input . autoRemove , "rm" , false , "automatically remove container(s)/volume(s) after a workflow(s) failure" )
2022-06-21 08:52:21 -05:00
rootCmd . Flags ( ) . StringArrayVarP ( & input . replaceGheActionWithGithubCom , "replace-ghe-action-with-github-com" , "" , [ ] string { } , "If you are using GitHub Enterprise Server and allow specified actions from GitHub (github.com), you can set actions on this. (e.g. --replace-ghe-action-with-github-com =github/super-linter)" )
rootCmd . Flags ( ) . StringVar ( & input . replaceGheActionTokenWithGithubCom , "replace-ghe-action-token-with-github-com" , "" , "If you are using replace-ghe-action-with-github-com and you want to use private actions on GitHub, you have to set personal access token" )
2023-03-19 12:25:55 -05:00
rootCmd . Flags ( ) . StringArrayVarP ( & input . matrix , "matrix" , "" , [ ] string { } , "specify which matrix configuration to include (e.g. --matrix java:13" )
2020-05-12 02:14:56 -05:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . actor , "actor" , "a" , "nektos/act" , "user that triggered the event" )
2020-05-26 22:29:50 -05:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . workflowsPath , "workflows" , "W" , "./.github/workflows/" , "path to workflow file(s)" )
2021-05-03 09:57:24 -05:00
rootCmd . PersistentFlags ( ) . BoolVarP ( & input . noWorkflowRecurse , "no-recurse" , "" , false , "Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag" )
2020-02-07 00:17:58 -06:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . workdir , "directory" , "C" , "." , "working directory" )
2019-01-17 02:15:35 -06:00
rootCmd . PersistentFlags ( ) . BoolP ( "verbose" , "v" , false , "verbose output" )
2022-03-14 10:33:11 -05:00
rootCmd . PersistentFlags ( ) . BoolVar ( & input . jsonLogger , "json" , false , "Output logs in json format" )
2020-02-20 10:57:18 -06:00
rootCmd . PersistentFlags ( ) . BoolVarP ( & input . noOutput , "quiet" , "q" , false , "disable logging of output from steps" )
2020-02-04 18:38:41 -06:00
rootCmd . PersistentFlags ( ) . BoolVarP ( & input . dryrun , "dryrun" , "n" , false , "dryrun mode" )
2021-01-19 08:31:46 -06:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . secretfile , "secret-file" , "" , ".secrets" , "file with list of secrets to read from (e.g. --secret-file .secrets)" )
2023-06-10 13:09:27 -05:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . varfile , "var-file" , "" , ".vars" , "file with list of vars to read from (e.g. --var-file .vars)" )
2021-01-12 00:28:45 -06:00
rootCmd . PersistentFlags ( ) . BoolVarP ( & input . insecureSecrets , "insecure-secrets" , "" , false , "NOT RECOMMENDED! Doesn't hide secrets while printing logs." )
2020-04-17 12:04:40 -05:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . envfile , "env-file" , "" , ".env" , "environment file to read and use as env in the containers" )
2023-01-13 13:28:17 -06:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . inputfile , "input-file" , "" , ".input" , "input file to read and use as action input" )
2021-05-02 10:15:13 -05:00
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." )
2023-04-25 11:31:17 -05:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . containerDaemonSocket , "container-daemon-socket" , "" , "" , "URI to Docker Engine socket (e.g.: unix://~/.docker/run/docker.sock or - to disable bind mounting the socket)" )
2022-12-06 09:58:47 -06:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . containerOptions , "container-options" , "" , "" , "Custom docker container options for the job container without an options property in the job definition" )
2021-05-05 11:42:34 -05:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . githubInstance , "github-instance" , "" , "github.com" , "GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server." )
2021-11-10 11:57:22 -06:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . artifactServerPath , "artifact-server-path" , "" , "" , "Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start." )
2023-01-16 08:12:20 -06:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . artifactServerAddr , "artifact-server-addr" , "" , common . GetOutboundIP ( ) . String ( ) , "Defines the address to which the artifact server binds." )
rootCmd . PersistentFlags ( ) . StringVarP ( & input . artifactServerPort , "artifact-server-port" , "" , "34567" , "Defines the port where the artifact server listens." )
2022-03-21 06:23:06 -05:00
rootCmd . PersistentFlags ( ) . BoolVarP ( & input . noSkipCheckout , "no-skip-checkout" , "" , false , "Do not skip actions/checkout" )
2023-04-28 10:57:40 -05:00
rootCmd . PersistentFlags ( ) . BoolVarP ( & input . noCacheServer , "no-cache-server" , "" , false , "Disable cache server" )
rootCmd . PersistentFlags ( ) . StringVarP ( & input . cacheServerPath , "cache-server-path" , "" , filepath . Join ( CacheHomeDir , "actcache" ) , "Defines the path where the cache server stores caches." )
rootCmd . PersistentFlags ( ) . StringVarP ( & input . cacheServerAddr , "cache-server-addr" , "" , common . GetOutboundIP ( ) . String ( ) , "Defines the address to which the cache server binds." )
rootCmd . PersistentFlags ( ) . Uint16VarP ( & input . cacheServerPort , "cache-server-port" , "" , 0 , "Defines the port where the artifact server listens. 0 means a randomly available port." )
2023-06-14 20:16:00 -05:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . actionCachePath , "action-cache-path" , "" , filepath . Join ( CacheHomeDir , "act" ) , "Defines the path where the actions get cached and host workspaces created." )
2020-02-25 00:11:33 -06:00
rootCmd . SetArgs ( args ( ) )
2020-03-06 14:30:24 -06:00
2019-01-12 22:45:25 -06:00
if err := rootCmd . Execute ( ) ; err != nil {
os . Exit ( 1 )
}
}
2021-04-05 10:51:13 -05:00
func configLocations ( ) [ ] string {
2023-03-03 08:39:02 -06:00
configFileName := ".actrc"
2021-04-05 10:51:13 -05:00
// reference: https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
var actrcXdg string
2023-03-03 08:39:02 -06:00
for _ , fileName := range [ ] string { "act/actrc" , configFileName } {
if foundConfig , err := xdg . SearchConfigFile ( fileName ) ; foundConfig != "" && err == nil {
actrcXdg = foundConfig
break
}
2021-04-05 10:51:13 -05:00
}
return [ ] string {
2023-04-28 10:57:40 -05:00
filepath . Join ( UserHomeDir , configFileName ) ,
2021-04-05 10:51:13 -05:00
actrcXdg ,
2023-03-03 08:39:02 -06:00
filepath . Join ( "." , configFileName ) ,
2021-01-19 08:30:17 -06:00
}
2021-04-05 10:51:13 -05:00
}
2023-04-23 14:02:56 -05:00
var commonSocketPaths = [ ] string {
"/var/run/docker.sock" ,
"/var/run/podman/podman.sock" ,
"$HOME/.colima/docker.sock" ,
"$XDG_RUNTIME_DIR/docker.sock" ,
` \\.\pipe\docker_engine ` ,
"$HOME/.docker/run/docker.sock" ,
}
// returns socket path or false if not found any
func socketLocation ( ) ( string , bool ) {
if dockerHost , exists := os . LookupEnv ( "DOCKER_HOST" ) ; exists {
return dockerHost , true
}
for _ , p := range commonSocketPaths {
if _ , err := os . Lstat ( os . ExpandEnv ( p ) ) ; err == nil {
if strings . HasPrefix ( p , ` \\.\ ` ) {
2023-04-25 11:31:17 -05:00
return "npipe://" + filepath . ToSlash ( os . ExpandEnv ( p ) ) , true
2023-04-23 14:02:56 -05:00
}
2023-04-25 11:31:17 -05:00
return "unix://" + filepath . ToSlash ( os . ExpandEnv ( p ) ) , true
2023-04-23 14:02:56 -05:00
}
}
return "" , false
}
2021-04-05 10:51:13 -05:00
func args ( ) [ ] string {
actrc := configLocations ( )
args := make ( [ ] string , 0 )
2021-01-19 08:30:17 -06:00
for _ , f := range actrc {
2022-03-22 14:26:10 -05:00
args = append ( args , readArgsFile ( f , true ) ... )
2020-02-25 00:11:33 -06:00
}
2021-04-05 10:51:13 -05:00
2020-02-25 00:11:33 -06:00
args = append ( args , os . Args [ 1 : ] ... )
return args
}
2022-03-22 14:26:10 -05:00
func bugReport ( ctx context . Context , version string ) error {
sprintf := func ( key , val string ) string {
return fmt . Sprintf ( "%-24s%s\n" , key , val )
}
report := sprintf ( "act version:" , version )
report += sprintf ( "GOOS:" , runtime . GOOS )
report += sprintf ( "GOARCH:" , runtime . GOARCH )
report += sprintf ( "NumCPU:" , fmt . Sprint ( runtime . NumCPU ( ) ) )
var dockerHost string
2023-04-23 14:02:56 -05:00
var exists bool
if dockerHost , exists = os . LookupEnv ( "DOCKER_HOST" ) ; ! exists {
dockerHost = "DOCKER_HOST environment variable is not set"
} else if dockerHost == "" {
dockerHost = "DOCKER_HOST environment variable is empty."
2022-03-22 14:26:10 -05:00
}
report += sprintf ( "Docker host:" , dockerHost )
report += fmt . Sprintln ( "Sockets found:" )
for _ , p := range commonSocketPaths {
2023-04-23 14:02:56 -05:00
if _ , err := os . Lstat ( os . ExpandEnv ( p ) ) ; err != nil {
2022-03-22 14:26:10 -05:00
continue
2023-04-23 14:02:56 -05:00
} else if _ , err := os . Stat ( os . ExpandEnv ( p ) ) ; err != nil {
report += fmt . Sprintf ( "\t%s(broken)\n" , p )
2022-03-22 14:26:10 -05:00
} else {
report += fmt . Sprintf ( "\t%s\n" , p )
}
}
report += sprintf ( "Config files:" , "" )
for _ , c := range configLocations ( ) {
args := readArgsFile ( c , false )
if len ( args ) > 0 {
report += fmt . Sprintf ( "\t%s:\n" , c )
for _ , l := range args {
report += fmt . Sprintf ( "\t\t%s\n" , l )
}
}
}
2022-05-17 14:02:15 -05:00
vcs , ok := debug . ReadBuildInfo ( )
if ok && vcs != nil {
report += fmt . Sprintln ( "Build info:" )
vcs := * vcs
report += sprintf ( "\tGo version:" , vcs . GoVersion )
report += sprintf ( "\tModule path:" , vcs . Path )
report += sprintf ( "\tMain version:" , vcs . Main . Version )
report += sprintf ( "\tMain path:" , vcs . Main . Path )
report += sprintf ( "\tMain checksum:" , vcs . Main . Sum )
report += fmt . Sprintln ( "\tBuild settings:" )
for _ , set := range vcs . Settings {
report += sprintf ( fmt . Sprintf ( "\t\t%s:" , set . Key ) , set . Value )
}
}
info , err := container . GetHostInfo ( ctx )
if err != nil {
fmt . Println ( report )
return err
}
2022-03-22 14:26:10 -05:00
report += fmt . Sprintln ( "Docker Engine:" )
report += sprintf ( "\tEngine version:" , info . ServerVersion )
report += sprintf ( "\tEngine runtime:" , info . DefaultRuntime )
report += sprintf ( "\tCgroup version:" , info . CgroupVersion )
report += sprintf ( "\tCgroup driver:" , info . CgroupDriver )
report += sprintf ( "\tStorage driver:" , info . Driver )
report += sprintf ( "\tRegistry URI:" , info . IndexServerAddress )
report += sprintf ( "\tOS:" , info . OperatingSystem )
report += sprintf ( "\tOS type:" , info . OSType )
report += sprintf ( "\tOS version:" , info . OSVersion )
report += sprintf ( "\tOS arch:" , info . Architecture )
report += sprintf ( "\tOS kernel:" , info . KernelVersion )
report += sprintf ( "\tOS CPU:" , fmt . Sprint ( info . NCPU ) )
report += sprintf ( "\tOS memory:" , fmt . Sprintf ( "%d MB" , info . MemTotal / 1024 / 1024 ) )
report += fmt . Sprintln ( "\tSecurity options:" )
for _ , secopt := range info . SecurityOptions {
report += fmt . Sprintf ( "\t\t%s\n" , secopt )
}
fmt . Println ( report )
return nil
}
func readArgsFile ( file string , split bool ) [ ] string {
2020-02-25 00:11:33 -06:00
args := make ( [ ] string , 0 )
f , err := os . Open ( file )
if err != nil {
return args
}
2021-01-12 00:39:43 -06:00
defer func ( ) {
err := f . Close ( )
if err != nil {
log . Errorf ( "Failed to close args file: %v" , err )
}
} ( )
2020-02-25 00:11:33 -06:00
scanner := bufio . NewScanner ( f )
for scanner . Scan ( ) {
2022-03-02 15:41:57 -06:00
arg := strings . TrimSpace ( scanner . Text ( ) )
2022-03-22 14:26:10 -05:00
if strings . HasPrefix ( arg , "-" ) && split {
2020-03-02 10:11:46 -06:00
args = append ( args , regexp . MustCompile ( ` \s ` ) . Split ( arg , 2 ) ... )
2022-03-22 14:26:10 -05:00
} else if ! split {
args = append ( args , arg )
2020-02-25 00:11:33 -06:00
}
}
return args
}
2023-01-15 04:30:41 -06:00
func setup ( inputs * Input ) func ( * cobra . Command , [ ] string ) {
return func ( cmd * cobra . Command , _ [ ] string ) {
verbose , _ := cmd . Flags ( ) . GetBool ( "verbose" )
if verbose {
log . SetLevel ( log . DebugLevel )
}
loadVersionNotices ( cmd . Version )
}
}
func cleanup ( inputs * Input ) func ( * cobra . Command , [ ] string ) {
return func ( cmd * cobra . Command , _ [ ] string ) {
displayNotices ( inputs )
2019-01-17 02:15:35 -06:00
}
}
2023-01-13 13:28:17 -06:00
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
}
2023-04-18 08:35:31 -05:00
func readYamlFile ( file string ) ( map [ string ] string , error ) {
content , err := os . ReadFile ( file )
if err != nil {
return nil , err
}
ret := map [ string ] string { }
if err = yaml . Unmarshal ( content , & ret ) ; err != nil {
return nil , err
}
return ret , nil
}
2020-04-17 12:04:40 -05:00
func readEnvs ( path string , envs map [ string ] string ) bool {
if _ , err := os . Stat ( path ) ; err == nil {
2023-04-18 08:35:31 -05:00
var env map [ string ] string
if ext := filepath . Ext ( path ) ; ext == ".yml" || ext == ".yaml" {
env , err = readYamlFile ( path )
} else {
env , err = godotenv . Read ( path )
}
2020-04-17 12:04:40 -05:00
if err != nil {
log . Fatalf ( "Error loading from %s: %v" , path , err )
}
for k , v := range env {
envs [ k ] = v
}
return true
}
return false
}
2023-03-19 12:25:55 -05:00
func parseMatrix ( matrix [ ] string ) map [ string ] map [ string ] bool {
// each matrix entry should be of the form - string:string
r := regexp . MustCompile ( ":" )
matrixes := make ( map [ string ] map [ string ] bool )
for _ , m := range matrix {
matrix := r . Split ( m , 2 )
if len ( matrix ) < 2 {
log . Fatalf ( "Invalid matrix format. Failed to parse %s" , m )
} else {
if _ , ok := matrixes [ matrix [ 0 ] ] ; ! ok {
matrixes [ matrix [ 0 ] ] = make ( map [ string ] bool )
}
matrixes [ matrix [ 0 ] ] [ matrix [ 1 ] ] = true
}
}
return matrixes
}
2023-04-25 11:31:17 -05:00
func isDockerHostURI ( daemonPath string ) bool {
if protoIndex := strings . Index ( daemonPath , "://" ) ; protoIndex != - 1 {
scheme := daemonPath [ : protoIndex ]
if strings . IndexFunc ( scheme , func ( r rune ) bool {
return ( r < 'a' || r > 'z' ) && ( r < 'A' || r > 'Z' )
} ) == - 1 {
return true
}
}
return false
}
2021-05-06 08:30:12 -05:00
//nolint:gocyclo
2020-02-04 18:38:41 -06:00
func newRunCommand ( ctx context . Context , input * Input ) func ( * cobra . Command , [ ] string ) error {
2019-01-12 22:45:25 -06:00
return func ( cmd * cobra . Command , args [ ] string ) error {
2022-03-14 10:33:11 -05:00
if input . jsonLogger {
log . SetFormatter ( & log . JSONFormatter { } )
}
2022-03-22 14:26:10 -05:00
if ok , _ := cmd . Flags ( ) . GetBool ( "bug-report" ) ; ok {
return bugReport ( ctx , cmd . Version )
}
2023-04-25 11:31:17 -05:00
// Prefer DOCKER_HOST, don't override it
socketPath , hasDockerHost := os . LookupEnv ( "DOCKER_HOST" )
if ! hasDockerHost {
// a - in containerDaemonSocket means don't mount, preserve this value
// otherwise if input.containerDaemonSocket is a filepath don't use it as socketPath
skipMount := input . containerDaemonSocket == "-" || ! isDockerHostURI ( input . containerDaemonSocket )
if input . containerDaemonSocket != "" && ! skipMount {
socketPath = input . containerDaemonSocket
2023-04-23 14:02:56 -05:00
} else {
2023-04-25 11:31:17 -05:00
socket , found := socketLocation ( )
if ! found {
log . Errorln ( "daemon Docker Engine socket not found and containerDaemonSocket option was not set" )
} else {
socketPath = socket
}
if ! skipMount {
input . containerDaemonSocket = socketPath
}
2023-04-23 14:02:56 -05:00
}
2023-04-25 11:31:17 -05:00
os . Setenv ( "DOCKER_HOST" , socketPath )
2023-04-23 14:02:56 -05:00
}
2021-06-23 13:09:27 -05:00
if runtime . GOOS == "darwin" && runtime . GOARCH == "arm64" && input . containerArchitecture == "" {
l := log . New ( )
l . SetFormatter ( & log . TextFormatter {
DisableQuote : true ,
DisableTimestamp : true ,
} )
2023-04-18 22:00:33 -05:00
l . Warnf ( " \U000026A0 You are using Apple M-series chip and you have not specified container architecture, you might encounter issues while running act. If so, try running it with '--container-architecture linux/amd64'. \U000026A0 \n" )
2021-06-23 13:09:27 -05:00
}
2020-04-17 12:04:40 -05:00
log . Debugf ( "Loading environment from %s" , input . Envfile ( ) )
envs := make ( map [ string ] string )
2023-01-13 13:28:17 -06:00
_ = parseEnvs ( input . envs , envs )
2020-04-17 12:04:40 -05:00
_ = readEnvs ( input . Envfile ( ) , envs )
2023-01-13 13:28:17 -06:00
log . Debugf ( "Loading action inputs from %s" , input . Inputfile ( ) )
inputs := make ( map [ string ] string )
_ = parseEnvs ( input . inputs , inputs )
_ = readEnvs ( input . Inputfile ( ) , inputs )
2020-04-17 12:04:40 -05:00
log . Debugf ( "Loading secrets from %s" , input . Secretfile ( ) )
secrets := newSecrets ( input . secrets )
_ = readEnvs ( input . Secretfile ( ) , secrets )
2020-03-06 14:30:24 -06:00
2023-06-10 13:09:27 -05:00
log . Debugf ( "Loading vars from %s" , input . Varfile ( ) )
vars := newSecrets ( input . vars )
_ = readEnvs ( input . Varfile ( ) , vars )
2023-03-19 12:25:55 -05:00
matrixes := parseMatrix ( input . matrix )
log . Debugf ( "Evaluated matrix inclusions: %v" , matrixes )
2021-05-03 09:57:24 -05:00
planner , err := model . NewWorkflowPlanner ( input . WorkflowsPath ( ) , input . noWorkflowRecurse )
2019-01-17 02:15:35 -06:00
if err != nil {
return err
}
2020-02-04 18:38:41 -06:00
2022-09-29 00:59:52 -05:00
jobID , err := cmd . Flags ( ) . GetString ( "job" )
if err != nil {
return err
}
// check if we should just list the workflows
list , err := cmd . Flags ( ) . GetBool ( "list" )
if err != nil {
return err
}
// check if we should just draw the graph
graph , err := cmd . Flags ( ) . GetBool ( "graph" )
if err != nil {
return err
}
// collect all events from loaded workflows
2021-01-18 13:42:55 -06:00
events := planner . GetEvents ( )
2022-09-29 00:59:52 -05:00
// plan with filtered jobs - to be used for filtering only
var filterPlan * model . Plan
// Determine the event name to be filtered
2023-02-16 10:41:59 -06:00
var filterEventName string
2022-09-29 00:59:52 -05:00
if len ( args ) > 0 {
log . Debugf ( "Using first passed in arguments event for filtering: %s" , args [ 0 ] )
filterEventName = args [ 0 ]
} else if input . autodetectEvent && len ( events ) > 0 && len ( events [ 0 ] ) > 0 {
// set default event type to first event from many available
// this way user dont have to specify the event.
log . Debugf ( "Using first detected workflow event for filtering: %s" , events [ 0 ] )
filterEventName = events [ 0 ]
}
2023-02-16 10:41:59 -06:00
var plannerErr error
2022-09-29 00:59:52 -05:00
if jobID != "" {
log . Debugf ( "Preparing plan with a job: %s" , jobID )
2023-02-16 10:41:59 -06:00
filterPlan , plannerErr = planner . PlanJob ( jobID )
2022-09-29 00:59:52 -05:00
} else if filterEventName != "" {
log . Debugf ( "Preparing plan for a event: %s" , filterEventName )
2023-02-16 10:41:59 -06:00
filterPlan , plannerErr = planner . PlanEvent ( filterEventName )
2022-09-29 00:59:52 -05:00
} else {
log . Debugf ( "Preparing plan with all jobs" )
2023-02-16 10:41:59 -06:00
filterPlan , plannerErr = planner . PlanAll ( )
}
if filterPlan == nil && plannerErr != nil {
return plannerErr
2022-09-29 00:59:52 -05:00
}
if list {
2023-02-16 10:41:59 -06:00
err = printList ( filterPlan )
if err != nil {
return err
}
return plannerErr
2022-09-29 00:59:52 -05:00
}
if graph {
2023-02-16 10:41:59 -06:00
err = drawGraph ( filterPlan )
if err != nil {
return err
}
return plannerErr
2022-09-29 00:59:52 -05:00
}
// plan with triggered jobs
var plan * model . Plan
// Determine the event name to be triggered
var eventName string
if len ( args ) > 0 {
log . Debugf ( "Using first passed in arguments event: %s" , args [ 0 ] )
eventName = args [ 0 ]
} else if len ( events ) == 1 && len ( events [ 0 ] ) > 0 {
log . Debugf ( "Using the only detected workflow event: %s" , events [ 0 ] )
eventName = events [ 0 ]
} else if input . autodetectEvent && len ( events ) > 0 && len ( events [ 0 ] ) > 0 {
// set default event type to first event from many available
2020-02-04 18:38:41 -06:00
// this way user dont have to specify the event.
2022-09-29 00:59:52 -05:00
log . Debugf ( "Using first detected workflow event: %s" , events [ 0 ] )
2020-02-10 18:53:14 -06:00
eventName = events [ 0 ]
2021-01-18 13:42:55 -06:00
} else {
2022-09-29 00:59:52 -05:00
log . Debugf ( "Using default workflow event: push" )
eventName = "push"
2019-01-12 22:45:25 -06:00
}
2019-02-09 20:39:09 -06:00
2020-02-04 18:38:41 -06:00
// build the plan for this run
2022-09-29 00:59:52 -05:00
if jobID != "" {
2020-02-04 18:38:41 -06:00
log . Debugf ( "Planning job: %s" , jobID )
2023-02-16 10:41:59 -06:00
plan , plannerErr = planner . PlanJob ( jobID )
2020-02-04 18:38:41 -06:00
} else {
2022-09-29 00:59:52 -05:00
log . Debugf ( "Planning jobs for event: %s" , eventName )
2023-02-16 10:41:59 -06:00
plan , plannerErr = planner . PlanEvent ( eventName )
}
if plan == nil && plannerErr != nil {
return plannerErr
2019-02-15 10:34:19 -06:00
}
2020-09-02 09:56:44 -05:00
// check to see if the main branch was defined
defaultbranch , err := cmd . Flags ( ) . GetString ( "defaultbranch" )
if err != nil {
return err
}
2021-04-05 10:51:13 -05:00
// Check if platforms flag is set, if not, run default image survey
2021-01-29 08:40:13 -06:00
if len ( input . platforms ) == 0 {
2021-04-05 10:51:13 -05:00
cfgFound := false
cfgLocations := configLocations ( )
for _ , v := range cfgLocations {
_ , err := os . Stat ( v )
if os . IsExist ( err ) {
cfgFound = true
2021-01-29 08:40:13 -06:00
}
2021-04-05 10:51:13 -05:00
}
if ! cfgFound && len ( cfgLocations ) > 0 {
if err := defaultImageSurvey ( cfgLocations [ 0 ] ) ; err != nil {
2021-01-29 08:40:13 -06:00
log . Fatal ( err )
}
2022-03-22 14:26:10 -05:00
input . platforms = readArgsFile ( cfgLocations [ 0 ] , true )
2021-01-29 08:40:13 -06:00
}
}
2022-12-06 09:58:47 -06:00
deprecationWarning := "--%s is deprecated and will be removed soon, please switch to cli: `--container-options \"%[2]s\"` or `.actrc`: `--container-options %[2]s`."
if input . privileged {
log . Warnf ( deprecationWarning , "privileged" , "--privileged" )
}
if len ( input . usernsMode ) > 0 {
log . Warnf ( deprecationWarning , "userns" , fmt . Sprintf ( "--userns=%s" , input . usernsMode ) )
}
if len ( input . containerCapAdd ) > 0 {
log . Warnf ( deprecationWarning , "container-cap-add" , fmt . Sprintf ( "--cap-add=%s" , input . containerCapAdd ) )
}
if len ( input . containerCapDrop ) > 0 {
log . Warnf ( deprecationWarning , "container-cap-drop" , fmt . Sprintf ( "--cap-drop=%s" , input . containerCapDrop ) )
}
2021-01-29 08:40:13 -06:00
2020-02-04 18:38:41 -06:00
// run the plan
2020-02-07 00:17:58 -06:00
config := & runner . Config {
2022-06-21 08:52:21 -05:00
Actor : input . actor ,
EventName : eventName ,
EventPath : input . EventPath ( ) ,
DefaultBranch : defaultbranch ,
ForcePull : input . forcePull ,
ForceRebuild : input . forceRebuild ,
ReuseContainers : input . reuseContainers ,
Workdir : input . Workdir ( ) ,
2023-06-14 20:16:00 -05:00
ActionCacheDir : input . actionCachePath ,
2022-06-21 08:52:21 -05:00
BindWorkdir : input . bindWorkdir ,
LogOutput : ! input . noOutput ,
JSONLogger : input . jsonLogger ,
Env : envs ,
Secrets : secrets ,
2023-06-10 13:09:27 -05:00
Vars : vars ,
2023-01-13 13:28:17 -06:00
Inputs : inputs ,
2022-06-21 08:52:21 -05:00
Token : secrets [ "GITHUB_TOKEN" ] ,
InsecureSecrets : input . insecureSecrets ,
Platforms : input . newPlatforms ( ) ,
Privileged : input . privileged ,
UsernsMode : input . usernsMode ,
ContainerArchitecture : input . containerArchitecture ,
2023-04-25 11:31:17 -05:00
ContainerDaemonSocket : input . containerDaemonSocket ,
2022-12-06 09:58:47 -06:00
ContainerOptions : input . containerOptions ,
2022-06-21 08:52:21 -05:00
UseGitIgnore : input . useGitIgnore ,
GitHubInstance : input . githubInstance ,
ContainerCapAdd : input . containerCapAdd ,
ContainerCapDrop : input . containerCapDrop ,
AutoRemove : input . autoRemove ,
ArtifactServerPath : input . artifactServerPath ,
2023-01-16 08:12:20 -06:00
ArtifactServerAddr : input . artifactServerAddr ,
2022-06-21 08:52:21 -05:00
ArtifactServerPort : input . artifactServerPort ,
NoSkipCheckout : input . noSkipCheckout ,
RemoteName : input . remoteName ,
ReplaceGheActionWithGithubCom : input . replaceGheActionWithGithubCom ,
ReplaceGheActionTokenWithGithubCom : input . replaceGheActionTokenWithGithubCom ,
2023-03-19 12:25:55 -05:00
Matrix : matrixes ,
2020-02-07 00:17:58 -06:00
}
2021-01-12 00:39:43 -06:00
r , err := runner . New ( config )
2020-02-07 00:17:58 -06:00
if err != nil {
return err
}
2023-01-16 08:12:20 -06:00
cancel := artifacts . Serve ( ctx , input . artifactServerPath , input . artifactServerAddr , input . artifactServerPort )
2021-11-10 11:57:22 -06:00
2023-04-28 10:57:40 -05:00
const cacheURLKey = "ACTIONS_CACHE_URL"
var cacheHandler * artifactcache . Handler
if ! input . noCacheServer && envs [ cacheURLKey ] == "" {
var err error
cacheHandler , err = artifactcache . StartHandler ( input . cacheServerPath , input . cacheServerAddr , input . cacheServerPort , common . Logger ( ctx ) )
if err != nil {
return err
}
envs [ cacheURLKey ] = cacheHandler . ExternalURL ( ) + "/"
}
2020-02-07 00:17:58 -06:00
ctx = common . WithDryrun ( ctx , input . dryrun )
if watch , err := cmd . Flags ( ) . GetBool ( "watch" ) ; err != nil {
return err
} else if watch {
2023-02-16 10:41:59 -06:00
err = watchAndRun ( ctx , r . NewPlanExecutor ( plan ) )
if err != nil {
return err
}
return plannerErr
2020-02-07 00:17:58 -06:00
}
2021-11-10 11:57:22 -06:00
executor := r . NewPlanExecutor ( plan ) . Finally ( func ( ctx context . Context ) error {
cancel ( )
2023-04-28 10:57:40 -05:00
_ = cacheHandler . Close ( )
2021-11-10 11:57:22 -06:00
return nil
} )
2023-02-16 10:41:59 -06:00
err = executor ( ctx )
if err != nil {
return err
}
return plannerErr
2019-02-09 20:39:09 -06:00
}
}
2021-04-05 10:51:13 -05:00
func defaultImageSurvey ( actrc string ) error {
var answer string
confirmation := & survey . Select {
Message : "Please choose the default image you want to use with act:\n\n - Large size image: +20GB Docker image, includes almost all tools used on GitHub Actions (IMPORTANT: currently only ubuntu-18.04 platform is available)\n - Medium size image: ~500MB, includes only necessary tools to bootstrap actions and aims to be compatible with all actions\n - Micro size image: <200MB, contains only NodeJS required to bootstrap actions, doesn't work with all actions\n\nDefault image and other options can be changed manually in ~/.actrc (please refer to https://github.com/nektos/act#configuration for additional information about file structure)" ,
Help : "If you want to know why act asks you that, please go to https://github.com/nektos/act/issues/107" ,
Default : "Medium" ,
Options : [ ] string { "Large" , "Medium" , "Micro" } ,
}
err := survey . AskOne ( confirmation , & answer )
if err != nil {
return err
}
var option string
switch answer {
case "Large" :
2022-07-07 19:21:51 -05:00
option = "-P ubuntu-latest=catthehacker/ubuntu:full-latest\n-P ubuntu-latest=catthehacker/ubuntu:full-20.04\n-P ubuntu-18.04=catthehacker/ubuntu:full-18.04\n"
2021-04-05 10:51:13 -05:00
case "Medium" :
2022-07-07 19:21:51 -05:00
option = "-P ubuntu-latest=catthehacker/ubuntu:act-latest\n-P ubuntu-22.04=catthehacker/ubuntu:act-22.04\n-P ubuntu-20.04=catthehacker/ubuntu:act-20.04\n-P ubuntu-18.04=catthehacker/ubuntu:act-18.04\n"
2021-04-05 10:51:13 -05:00
case "Micro" :
2023-01-19 00:49:16 -06:00
option = "-P ubuntu-latest=node:16-buster-slim\n-P ubuntu-22.04=node:16-bullseye-slim\n-P ubuntu-20.04=node:16-buster-slim\n-P ubuntu-18.04=node:16-buster-slim\n"
2021-04-05 10:51:13 -05:00
}
f , err := os . Create ( actrc )
if err != nil {
return err
}
_ , err = f . WriteString ( option )
if err != nil {
_ = f . Close ( )
return err
}
err = f . Close ( )
if err != nil {
return err
}
return nil
}
2020-02-07 00:17:58 -06:00
func watchAndRun ( ctx context . Context , fn common . Executor ) error {
2019-02-09 20:39:09 -06:00
dir , err := os . Getwd ( )
if err != nil {
return err
}
2023-04-18 23:19:40 -05:00
ignoreFile := filepath . Join ( dir , ".gitignore" )
ignore := & gitignore . GitIgnore { }
if info , err := os . Stat ( ignoreFile ) ; err == nil && ! info . IsDir ( ) {
ignore , err = gitignore . CompileIgnoreFile ( ignoreFile )
if err != nil {
return fmt . Errorf ( "compile %q: %w" , ignoreFile , err )
}
2019-02-09 20:39:09 -06:00
}
folderWatcher := fswatch . NewFolderWatcher (
dir ,
2023-04-18 23:19:40 -05:00
true ,
2019-02-09 20:39:09 -06:00
ignore . MatchesPath ,
2023-04-18 23:19:40 -05:00
2 , // 2 seconds
2019-02-09 20:39:09 -06:00
)
folderWatcher . Start ( )
2023-04-18 23:19:40 -05:00
defer folderWatcher . Stop ( )
2019-02-09 20:39:09 -06:00
2023-04-18 23:19:40 -05:00
// run once before watching
if err := fn ( ctx ) ; err != nil {
return err
}
for folderWatcher . IsRunning ( ) {
log . Debugf ( "Watching %s for changes" , dir )
select {
case <- ctx . Done ( ) :
return nil
case changes := <- folderWatcher . ChangeDetails ( ) :
log . Debugf ( "%s" , changes . String ( ) )
if err := fn ( ctx ) ; err != nil {
return err
2019-02-09 20:39:09 -06:00
}
}
2023-04-18 23:19:40 -05:00
}
return nil
2019-01-12 22:45:25 -06:00
}