package actions import ( "io/ioutil" "os" "path/filepath" "sort" "github.com/nektos/act/common" log "github.com/sirupsen/logrus" ) type runnerImpl struct { config *RunnerConfig workflows *workflowsFile tempDir string eventJSON string } // NewRunner Creates a new Runner func NewRunner(runnerConfig *RunnerConfig) (Runner, error) { runner := &runnerImpl{ config: runnerConfig, } init := common.NewPipelineExecutor( runner.setupTempDir, runner.setupWorkingDir, runner.setupWorkflows, runner.setupEvent, ) return runner, init() } func (runner *runnerImpl) setupTempDir() error { var err error runner.tempDir, err = ioutil.TempDir("", "act-") return err } func (runner *runnerImpl) setupWorkingDir() error { var err error runner.config.WorkingDir, err = filepath.Abs(runner.config.WorkingDir) log.Debugf("Setting working dir to %s", runner.config.WorkingDir) return err } func (runner *runnerImpl) setupWorkflows() error { runner.config.WorkflowPath = runner.resolvePath(runner.config.WorkflowPath) log.Debugf("Loading workflow config from %s", runner.config.WorkflowPath) workflowReader, err := os.Open(runner.config.WorkflowPath) if err != nil { return err } defer workflowReader.Close() runner.workflows, err = parseWorkflowsFile(workflowReader) return err } func (runner *runnerImpl) setupEvent() error { runner.eventJSON = "{}" if runner.config.EventPath != "" { runner.config.EventPath = runner.resolvePath(runner.config.EventPath) log.Debugf("Reading event.json from %s", runner.config.EventPath) eventJSONBytes, err := ioutil.ReadFile(runner.config.EventPath) if err != nil { return err } runner.eventJSON = string(eventJSONBytes) } return nil } func (runner *runnerImpl) resolvePath(path string) string { if path == "" { return path } if !filepath.IsAbs(path) { path = filepath.Join(runner.config.WorkingDir, path) } return path } // ListEvents gets all the events in the workflows file func (runner *runnerImpl) ListEvents() []string { log.Debugf("Listing all events") events := make([]string, 0) for _, w := range runner.workflows.Workflow { events = append(events, w.On) } // sort the list based on depth of dependencies sort.Slice(events, func(i, j int) bool { return events[i] < events[j] }) return events } // GraphEvent builds an execution path func (runner *runnerImpl) GraphEvent(eventName string) ([][]string, error) { log.Debugf("Listing actions for event '%s'", eventName) workflow, _, err := runner.workflows.getWorkflow(eventName) if err != nil { return nil, err } return runner.workflows.newExecutionGraph(workflow.Resolves...), nil } // RunAction runs a set of actions in parallel, and their dependencies func (runner *runnerImpl) RunActions(actionNames ...string) error { log.Debugf("Running actions %+q", actionNames) graph := runner.workflows.newExecutionGraph(actionNames...) pipeline := make([]common.Executor, 0) for _, actions := range graph { stage := make([]common.Executor, 0) for _, actionName := range actions { stage = append(stage, runner.newActionExecutor(actionName)) } pipeline = append(pipeline, common.NewParallelExecutor(stage...)) } executor := common.NewPipelineExecutor(pipeline...) return executor() } // RunEvent runs the actions for a single event func (runner *runnerImpl) RunEvent() error { log.Debugf("Running event '%s'", runner.config.EventName) workflow, _, err := runner.workflows.getWorkflow(runner.config.EventName) if err != nil { return err } log.Debugf("Running actions %s -> %s", runner.config.EventName, workflow.Resolves) return runner.RunActions(workflow.Resolves...) } func (runner *runnerImpl) Close() error { return os.RemoveAll(runner.tempDir) }