2022-04-27 12:45:53 +03:00
package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
2022-07-21 04:36:17 +03:00
"github.com/mattn/go-isatty"
2022-04-27 12:45:53 +03:00
"github.com/nektos/act/pkg/artifacts"
2022-08-12 07:16:47 +03:00
"github.com/nektos/act/pkg/common"
2022-04-27 12:45:53 +03:00
"github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/runner"
2022-08-13 08:12:38 +03:00
log "github.com/sirupsen/logrus"
2022-04-27 12:45:53 +03:00
"github.com/spf13/cobra"
)
const version = "0.1"
type Input struct {
2022-08-13 09:15:21 +03:00
envFile string
actor string
2022-07-20 09:42:22 +03:00
// workdir string
// workflowsPath string
// autodetectEvent bool
// eventPath string
reuseContainers bool
bindWorkdir bool
// secrets []string
// envs []string
// platforms []string
// dryrun bool
forcePull bool
forceRebuild bool
// noOutput bool
// envfile string
// secretfile string
insecureSecrets bool
// defaultBranch string
2022-04-27 12:45:53 +03:00
privileged bool
usernsMode string
containerArchitecture string
containerDaemonSocket string
2022-07-20 09:42:22 +03:00
// noWorkflowRecurse bool
useGitIgnore bool
forgeInstance string
containerCapAdd [ ] string
containerCapDrop [ ] string
autoRemove bool
artifactServerPath string
artifactServerPort string
jsonLogger bool
noSkipCheckout bool
// remoteName string
2022-04-27 12:45:53 +03:00
}
func ( i * Input ) newPlatforms ( ) map [ string ] string {
return map [ string ] string {
"ubuntu-latest" : "node:16-buster-slim" ,
"ubuntu-20.04" : "node:16-buster-slim" ,
"ubuntu-18.04" : "node:16-buster-slim" ,
}
}
2022-08-13 08:12:38 +03:00
// initLogging setup the global logrus logger.
func initLogging ( cfg Config ) {
2022-07-21 04:36:17 +03:00
isTerm := isatty . IsTerminal ( os . Stdout . Fd ( ) )
2022-08-13 08:12:38 +03:00
log . SetFormatter ( & log . TextFormatter {
DisableColors : ! isTerm ,
FullTimestamp : true ,
} )
2022-07-21 04:36:17 +03:00
2022-08-13 08:12:38 +03:00
if cfg . Debug {
log . SetLevel ( log . DebugLevel )
2022-07-21 04:36:17 +03:00
}
2022-08-13 08:12:38 +03:00
if cfg . Trace {
log . SetLevel ( log . TraceLevel )
2022-07-21 04:36:17 +03:00
}
}
2022-04-27 12:45:53 +03:00
func Execute ( ctx context . Context ) {
2022-06-20 11:37:28 +03:00
input := Input {
reuseContainers : true ,
forgeInstance : "gitea.com" ,
}
2022-04-27 12:45:53 +03:00
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." ,
Args : cobra . MaximumNArgs ( 1 ) ,
RunE : runCommand ( ctx , & input ) ,
Version : version ,
SilenceUsage : true ,
}
2022-05-02 12:02:51 +03:00
rootCmd . AddCommand ( & cobra . Command {
Aliases : [ ] string { "daemon" } ,
Use : "daemon [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." ,
Args : cobra . MaximumNArgs ( 1 ) ,
RunE : runDaemon ( ctx , & input ) ,
} )
2022-04-27 12:45:53 +03:00
rootCmd . Flags ( ) . BoolP ( "run" , "r" , false , "run workflows" )
rootCmd . Flags ( ) . StringP ( "job" , "j" , "" , "run job" )
rootCmd . PersistentFlags ( ) . StringVarP ( & input . forgeInstance , "forge-instance" , "" , "github.com" , "Forge instance to use." )
2022-08-13 09:15:21 +03:00
rootCmd . PersistentFlags ( ) . StringVarP ( & input . envFile , "env-file" , "" , ".env" , "Read in a file of environment variables." )
2022-04-27 12:45:53 +03:00
if err := rootCmd . Execute ( ) ; err != nil {
os . Exit ( 1 )
}
}
// getWorkflowsPath return the workflows directory, it will try .gitea first and then fallback to .github
func getWorkflowsPath ( ) ( string , error ) {
dir , err := os . Getwd ( )
if err != nil {
return "" , err
}
p := filepath . Join ( dir , ".gitea/workflows" )
_ , err = os . Stat ( p )
if err != nil {
if ! os . IsNotExist ( err ) {
return "" , err
}
return filepath . Join ( dir , ".github/workflows" ) , nil
}
return p , nil
}
2022-05-02 12:02:51 +03:00
func runTask ( ctx context . Context , input * Input , jobID string ) error {
workflowsPath , err := getWorkflowsPath ( )
if err != nil {
return err
}
planner , err := model . NewWorkflowPlanner ( workflowsPath , false )
if err != nil {
return err
}
2022-04-27 12:45:53 +03:00
2022-05-02 12:02:51 +03:00
var eventName string
events := planner . GetEvents ( )
if len ( events ) > 0 {
// set default event type to first event
// this way user dont have to specify the event.
2022-08-13 08:12:38 +03:00
log . Debugf ( "Using detected workflow event: %s" , events [ 0 ] )
2022-05-02 12:02:51 +03:00
eventName = events [ 0 ]
} else {
if plan := planner . PlanEvent ( "push" ) ; plan != nil {
eventName = "push"
2022-04-27 12:45:53 +03:00
}
2022-05-02 12:02:51 +03:00
}
2022-04-27 12:45:53 +03:00
2022-05-02 12:02:51 +03:00
// build the plan for this run
var plan * model . Plan
if jobID != "" {
2022-08-13 08:12:38 +03:00
log . Debugf ( "Planning job: %s" , jobID )
2022-05-02 12:02:51 +03:00
plan = planner . PlanJob ( jobID )
} else {
2022-08-13 08:12:38 +03:00
log . Debugf ( "Planning event: %s" , eventName )
2022-05-02 12:02:51 +03:00
plan = planner . PlanEvent ( eventName )
}
2022-04-27 12:45:53 +03:00
2022-05-02 12:02:51 +03:00
curDir , err := os . Getwd ( )
if err != nil {
return err
}
2022-04-27 12:45:53 +03:00
2022-05-02 12:02:51 +03:00
// run the plan
config := & runner . Config {
Actor : input . actor ,
EventName : eventName ,
EventPath : "" ,
DefaultBranch : "" ,
ForcePull : input . forcePull ,
ForceRebuild : input . forceRebuild ,
ReuseContainers : input . reuseContainers ,
Workdir : curDir ,
BindWorkdir : input . bindWorkdir ,
LogOutput : true ,
JSONLogger : input . jsonLogger ,
// Env: envs,
// Secrets: secrets,
InsecureSecrets : input . insecureSecrets ,
Platforms : input . newPlatforms ( ) ,
Privileged : input . privileged ,
UsernsMode : input . usernsMode ,
ContainerArchitecture : input . containerArchitecture ,
ContainerDaemonSocket : input . containerDaemonSocket ,
UseGitIgnore : input . useGitIgnore ,
GitHubInstance : input . forgeInstance ,
ContainerCapAdd : input . containerCapAdd ,
ContainerCapDrop : input . containerCapDrop ,
AutoRemove : input . autoRemove ,
ArtifactServerPath : input . artifactServerPath ,
ArtifactServerPort : input . artifactServerPort ,
NoSkipCheckout : input . noSkipCheckout ,
// RemoteName: input.remoteName,
}
r , err := runner . New ( config )
if err != nil {
return fmt . Errorf ( "New config failed: %v" , err )
}
cancel := artifacts . Serve ( ctx , input . artifactServerPath , input . artifactServerPort )
executor := r . NewPlanExecutor ( plan ) . Finally ( func ( ctx context . Context ) error {
cancel ( )
return nil
} )
2022-08-12 07:16:47 +03:00
outputHook := new ( taskLogHook )
ctx = common . WithLoggerHook ( ctx , outputHook )
2022-05-02 12:02:51 +03:00
return executor ( ctx )
}
2022-08-12 07:16:47 +03:00
type taskLogHook struct { }
2022-08-13 08:12:38 +03:00
func ( h * taskLogHook ) Levels ( ) [ ] log . Level {
return log . AllLevels
2022-08-12 07:16:47 +03:00
}
2022-08-13 08:12:38 +03:00
func ( h * taskLogHook ) Fire ( entry * log . Entry ) error {
2022-08-12 07:16:47 +03:00
if flag , ok := entry . Data [ "raw_output" ] ; ok {
if flagVal , ok := flag . ( bool ) ; flagVal && ok {
2022-08-13 08:12:38 +03:00
log . Infof ( "task log: %s" , entry . Message )
2022-08-12 07:16:47 +03:00
}
}
return nil
}
2022-05-02 12:02:51 +03:00
func runCommand ( ctx context . Context , input * Input ) func ( cmd * cobra . Command , args [ ] string ) error {
return func ( cmd * cobra . Command , args [ ] string ) error {
jobID , err := cmd . Flags ( ) . GetString ( "job" )
2022-04-27 12:45:53 +03:00
if err != nil {
2022-05-02 12:02:51 +03:00
return err
2022-04-27 12:45:53 +03:00
}
2022-05-02 12:02:51 +03:00
return runTask ( ctx , input , jobID )
2022-04-27 12:45:53 +03:00
}
}