1
0
Fork 0
mirror of https://code.forgejo.org/forgejo/runner.git synced 2025-08-06 17:40:58 +00:00

initial load of yaml working

This commit is contained in:
Casey Lee 2020-02-04 16:38:41 -08:00
parent 113ebda3ff
commit fbab49c68d
28 changed files with 522 additions and 393 deletions

8
.github/workflows/basic.yml vendored Normal file
View file

@ -0,0 +1,8 @@
name: basic
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hello world!

View file

@ -10,7 +10,7 @@ import (
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/nektos/act/common"
"github.com/nektos/act/pkg/common"
log "github.com/sirupsen/logrus"
)

View file

@ -6,7 +6,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/nektos/act/common"
"github.com/nektos/act/pkg/common"
)
// NewDockerPullExecutorInput the input for the NewDockerPullExecutor function

View file

@ -9,7 +9,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/nektos/act/common"
"github.com/nektos/act/pkg/common"
"golang.org/x/crypto/ssh/terminal"
)

View file

@ -3,11 +3,12 @@ package container
import (
"bytes"
"context"
"github.com/nektos/act/common"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"io/ioutil"
"testing"
"github.com/nektos/act/pkg/common"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
type rawFormatter struct{}

196
act/model/planner.go Normal file
View file

@ -0,0 +1,196 @@
package model
import (
"io/ioutil"
"math"
"os"
"path/filepath"
"sort"
log "github.com/sirupsen/logrus"
)
// WorkflowPlanner contains methods for creating plans
type WorkflowPlanner interface {
PlanEvent(eventName string) *Plan
PlanJob(jobName string) *Plan
GetEvents() []string
}
// Plan contains a list of stages to run in series
type Plan struct {
Stages []*Stage
}
// Stage contains a list of runs to execute in parallel
type Stage struct {
Runs []*Run
}
// Run represents a job from a workflow that needs to be run
type Run struct {
Workflow *Workflow
JobID string
}
// NewWorkflowPlanner will load all workflows from a directory
func NewWorkflowPlanner(dirname string) (WorkflowPlanner, error) {
log.Debugf("Loading workflows from '%s'", dirname)
files, err := ioutil.ReadDir(dirname)
if err != nil {
return nil, err
}
wp := new(workflowPlanner)
for _, file := range files {
ext := filepath.Ext(file.Name())
if ext == ".yml" || ext == ".yaml" {
f, err := os.Open(filepath.Join(dirname, file.Name()))
if err != nil {
return nil, err
}
workflow, err := ReadWorkflow(f)
if err != nil {
f.Close()
return nil, err
}
wp.workflows = append(wp.workflows, workflow)
f.Close()
}
}
return wp, nil
}
type workflowPlanner struct {
workflows []*Workflow
}
// PlanEvent builds a new list of runs to execute in parallel for an event name
func (wp *workflowPlanner) PlanEvent(eventName string) *Plan {
plan := new(Plan)
for _, w := range wp.workflows {
if w.On == eventName {
plan.mergeStages(createStages(w, w.GetJobIDs()...))
}
}
return plan
}
// PlanJob builds a new run to execute in parallel for a job name
func (wp *workflowPlanner) PlanJob(jobName string) *Plan {
plan := new(Plan)
for _, w := range wp.workflows {
plan.mergeStages(createStages(w, jobName))
}
return plan
}
// GetEvents gets all the events in the workflows file
func (wp *workflowPlanner) GetEvents() []string {
events := make([]string, 0)
for _, w := range wp.workflows {
found := false
for _, e := range events {
if e == w.On {
found = true
break
}
}
if !found {
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
}
// GetJobIDs will get all the job names in the stage
func (s *Stage) GetJobIDs() []string {
names := make([]string, 0)
for _, r := range s.Runs {
names = append(names, r.JobID)
}
return names
}
// Merge stages with existing stages in plan
func (p *Plan) mergeStages(stages []*Stage) {
newStages := make([]*Stage, int(math.Max(float64(len(p.Stages)), float64(len(stages)))))
for i := 0; i < len(newStages); i++ {
newStages[i] = new(Stage)
if i >= len(p.Stages) {
newStages[i].Runs = append(stages[i].Runs)
} else if i >= len(stages) {
newStages[i].Runs = append(p.Stages[i].Runs)
} else {
newStages[i].Runs = append(p.Stages[i].Runs, stages[i].Runs...)
}
}
p.Stages = newStages
}
func createStages(w *Workflow, jobIDs ...string) []*Stage {
// first, build a list of all the necessary jobs to run, and their dependencies
jobDependencies := make(map[string][]string)
for len(jobIDs) > 0 {
newJobIDs := make([]string, 0)
for _, jID := range jobIDs {
// make sure we haven't visited this job yet
if _, ok := jobDependencies[jID]; !ok {
if job := w.GetJob(jID); job != nil {
jobDependencies[jID] = job.Needs
newJobIDs = append(newJobIDs, job.Needs...)
}
}
}
jobIDs = newJobIDs
}
// next, build an execution graph
stages := make([]*Stage, 0)
for len(jobDependencies) > 0 {
stage := new(Stage)
for jID, jDeps := range jobDependencies {
// make sure all deps are in the graph already
if listInStages(jDeps, stages...) {
stage.Runs = append(stage.Runs, &Run{
Workflow: w,
JobID: jID,
})
delete(jobDependencies, jID)
}
}
if len(stage.Runs) == 0 {
log.Fatalf("Unable to build dependency graph!")
}
stages = append(stages, stage)
}
return stages
}
// return true iff all strings in srcList exist in at least one of the stages
func listInStages(srcList []string, stages ...*Stage) bool {
for _, src := range srcList {
found := false
for _, stage := range stages {
for _, search := range stage.GetJobIDs() {
if src == search {
found = true
}
}
}
if !found {
return false
}
}
return true
}

67
act/model/workflow.go Normal file
View file

@ -0,0 +1,67 @@
package model
import (
"io"
"gopkg.in/yaml.v2"
)
// Workflow is the structure of the files in .github/workflows
type Workflow struct {
Name string `yaml:"name"`
On string `yaml:"on"`
Env map[string]string `yaml:"env"`
Jobs map[string]*Job `yaml:"jobs"`
}
// Job is the structure of one job in a workflow
type Job struct {
Name string `yaml:"name"`
Needs []string `yaml:"needs"`
RunsOn string `yaml:"runs-on"`
Env map[string]string `yaml:"env"`
If string `yaml:"if"`
Steps []*Step `yaml:"steps"`
TimeoutMinutes int64 `yaml:"timeout-minutes"`
}
// Step is the structure of one step in a job
type Step struct {
ID string `yaml:"id"`
If string `yaml:"if"`
Name string `yaml:"name"`
Uses string `yaml:"uses"`
Run string `yaml:"run"`
WorkingDirectory string `yaml:"working-directory"`
Shell string `yaml:"shell"`
Env map[string]string `yaml:"env"`
With map[string]string `yaml:"with"`
ContinueOnError bool `yaml:"continue-on-error"`
TimeoutMinutes int64 `yaml:"timeout-minutes"`
}
// ReadWorkflow returns a list of jobs for a given workflow file reader
func ReadWorkflow(in io.Reader) (*Workflow, error) {
w := new(Workflow)
err := yaml.NewDecoder(in).Decode(w)
return w, err
}
// GetJob will get a job by name in the workflow
func (w *Workflow) GetJob(jobID string) *Job {
for id, j := range w.Jobs {
if jobID == id {
return j
}
}
return nil
}
// GetJobIDs will get all the job names in the workflow
func (w *Workflow) GetJobIDs() []string {
ids := make([]string, 0)
for id := range w.Jobs {
ids = append(ids, id)
}
return ids
}

5
act/runner/api.go Normal file
View file

@ -0,0 +1,5 @@
package runner
type environmentApplier interface {
applyEnvironment(map[string]string)
}

88
act/runner/runner.go Normal file
View file

@ -0,0 +1,88 @@
package runner
import (
"io"
"io/ioutil"
"os"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus"
)
// Runner provides capabilities to run GitHub actions
type Runner interface {
PlanRunner
io.Closer
}
// PlanRunner to run a specific actions
type PlanRunner interface {
RunPlan(plan *model.Plan) error
}
// Config contains the config for a new runner
type Config struct {
Dryrun bool // don't start any of the containers
EventName string // name of event to run
EventPath string // path to JSON file to use for event.json in containers
ReuseContainers bool // reuse containers to maintain state
ForcePull bool // force pulling of the image, if already present
}
type runnerImpl struct {
config *Config
tempDir string
eventJSON string
}
// NewRunner Creates a new Runner
func NewRunner(runnerConfig *Config) (Runner, error) {
runner := &runnerImpl{
config: runnerConfig,
}
init := common.NewPipelineExecutor(
runner.setupTempDir,
runner.setupEvent,
)
return runner, init()
}
func (runner *runnerImpl) setupTempDir() error {
var err error
runner.tempDir, err = ioutil.TempDir("", "act-")
return err
}
func (runner *runnerImpl) setupEvent() error {
runner.eventJSON = "{}"
if 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) RunPlan(plan *model.Plan) error {
pipeline := make([]common.Executor, 0)
for _, stage := range plan.Stages {
stageExecutor := make([]common.Executor, 0)
for _, run := range stage.Runs {
stageExecutor = append(stageExecutor, runner.newRunExecutor(run))
}
pipeline = append(pipeline, common.NewParallelExecutor(stageExecutor...))
}
executor := common.NewPipelineExecutor(pipeline...)
return executor()
}
func (runner *runnerImpl) Close() error {
return os.RemoveAll(runner.tempDir)
}

View file

@ -1,21 +1,20 @@
package actions
package runner
import (
"archive/tar"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"github.com/actions/workflow-parser/model"
"github.com/nektos/act/common"
"github.com/nektos/act/container"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus"
)
func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
func (runner *runnerImpl) newRunExecutor(run *model.Run) common.Executor {
action := runner.workflowConfig.GetAction(actionName)
if action == nil {
return common.NewErrorExecutor(fmt.Errorf("Unable to find action named '%s'", actionName))
@ -35,7 +34,8 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
return common.NewPipelineExecutor(executors...)
}
func (runner *runnerImpl) addImageExecutor(action *model.Action, executors *[]common.Executor) (string, error) {
/*
func (runner *runnerImpl) addImageExecutor(action *Action, executors *[]common.Executor) (string, error) {
var image string
logger := newActionLogger(action.Identifier, runner.config.Dryrun)
log.Debugf("Using '%s' for action '%s'", action.Uses, action.Identifier)
@ -111,8 +111,9 @@ func (runner *runnerImpl) addImageExecutor(action *model.Action, executors *[]co
return image, nil
}
*/
func (runner *runnerImpl) addRunExecutor(action *model.Action, image string, executors *[]common.Executor) error {
func (runner *runnerImpl) addRunExecutor(action *Action, image string, executors *[]common.Executor) error {
logger := newActionLogger(action.Identifier, runner.config.Dryrun)
log.Debugf("Using '%s' for action '%s'", action.Uses, action.Identifier)

View file

@ -1,4 +1,4 @@
package actions
package runner
import (
"context"

View file

@ -5,17 +5,18 @@ import (
"log"
"os"
"github.com/actions/workflow-parser/model"
"github.com/howeyc/gopass"
)
var secretCache map[string]string
type actionEnvironmentApplier struct {
*model.Action
*Action
}
func newActionEnvironmentApplier(action *model.Action) environmentApplier {
type Action struct{}
func newActionEnvironmentApplier(action *Action) environmentApplier {
return &actionEnvironmentApplier{action}
}

View file

@ -1,51 +0,0 @@
package actions
import (
"context"
"io"
)
// Runner provides capabilities to run GitHub actions
type Runner interface {
EventGrapher
EventLister
EventRunner
ActionRunner
io.Closer
}
// EventGrapher to list the actions
type EventGrapher interface {
GraphEvent(eventName string) ([][]string, error)
}
// EventLister to list the events
type EventLister interface {
ListEvents() []string
}
// EventRunner to run the actions for a given event
type EventRunner interface {
RunEvent() error
}
// ActionRunner to run a specific actions
type ActionRunner interface {
RunActions(actionNames ...string) error
}
// RunnerConfig contains the config for a new runner
type RunnerConfig struct {
Ctx context.Context // context to use for the run
Dryrun bool // don't start any of the containers
WorkingDir string // base directory to use
WorkflowPath string // path to load main.workflow file, relative to WorkingDir
EventName string // name of event to run
EventPath string // path to JSON file to use for event.json in containers, relative to WorkingDir
ReuseContainers bool // reuse containers to maintain state
ForcePull bool // force pulling of the image, if already present
}
type environmentApplier interface {
applyEnvironment(map[string]string)
}

View file

@ -1,64 +0,0 @@
package actions
import (
"log"
"github.com/actions/workflow-parser/model"
)
// return a pipeline that is run in series. pipeline is a list of steps to run in parallel
func newExecutionGraph(workflowConfig *model.Configuration, actionNames ...string) [][]string {
// first, build a list of all the necessary actions to run, and their dependencies
actionDependencies := make(map[string][]string)
for len(actionNames) > 0 {
newActionNames := make([]string, 0)
for _, aName := range actionNames {
// make sure we haven't visited this action yet
if _, ok := actionDependencies[aName]; !ok {
action := workflowConfig.GetAction(aName)
if action != nil {
actionDependencies[aName] = action.Needs
newActionNames = append(newActionNames, action.Needs...)
}
}
}
actionNames = newActionNames
}
// next, build an execution graph
graph := make([][]string, 0)
for len(actionDependencies) > 0 {
stage := make([]string, 0)
for aName, aDeps := range actionDependencies {
// make sure all deps are in the graph already
if listInLists(aDeps, graph...) {
stage = append(stage, aName)
delete(actionDependencies, aName)
}
}
if len(stage) == 0 {
log.Fatalf("Unable to build dependency graph!")
}
graph = append(graph, stage)
}
return graph
}
// return true iff all strings in srcList exist in at least one of the searchLists
func listInLists(srcList []string, searchLists ...[]string) bool {
for _, src := range srcList {
found := false
for _, searchList := range searchLists {
for _, search := range searchList {
if src == search {
found = true
}
}
}
if !found {
return false
}
}
return true
}

View file

@ -1,160 +0,0 @@
package actions
import (
"io/ioutil"
"os"
"path/filepath"
"sort"
"github.com/actions/workflow-parser/model"
"github.com/actions/workflow-parser/parser"
"github.com/nektos/act/common"
log "github.com/sirupsen/logrus"
)
type runnerImpl struct {
config *RunnerConfig
workflowConfig *model.Configuration
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.workflowConfig, err = parser.Parse(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.workflowConfig.Workflows {
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)
resolves := runner.resolveEvent(eventName)
return newExecutionGraph(runner.workflowConfig, 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 := newExecutionGraph(runner.workflowConfig, 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)
resolves := runner.resolveEvent(runner.config.EventName)
log.Debugf("Running actions %s -> %s", runner.config.EventName, resolves)
return runner.RunActions(resolves...)
}
func (runner *runnerImpl) Close() error {
return os.RemoveAll(runner.tempDir)
}
// get list of resolves for an event
func (runner *runnerImpl) resolveEvent(eventName string) []string {
workflows := runner.workflowConfig.GetWorkflows(eventName)
resolves := make([]string, 0)
for _, workflow := range workflows {
for _, resolve := range workflow.Resolves {
found := false
for _, r := range resolves {
if r == resolve {
found = true
break
}
}
if !found {
resolves = append(resolves, resolve)
}
}
}
return resolves
}

40
cmd/graph.go Normal file
View file

@ -0,0 +1,40 @@
package cmd
import (
"fmt"
"os"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model"
)
func drawGraph(plan *model.Plan) error {
drawings := make([]*common.Drawing, 0)
jobPen := common.NewPen(common.StyleSingleLine, 96)
arrowPen := common.NewPen(common.StyleNoLine, 97)
for i, stage := range plan.Stages {
if i > 0 {
drawings = append(drawings, arrowPen.DrawArrow())
}
ids := make([]string, 0)
for _, r := range stage.Runs {
ids = append(ids, fmt.Sprintf("%s/%s", r.Workflow.Name, r.JobID))
}
drawings = append(drawings, jobPen.DrawBoxes(ids...))
}
maxWidth := 0
for _, d := range drawings {
if d.GetWidth() > maxWidth {
maxWidth = d.GetWidth()
}
}
for _, d := range drawings {
d.Draw(os.Stdout, maxWidth)
}
return nil
}

40
cmd/input.go Normal file
View file

@ -0,0 +1,40 @@
package cmd
import (
"log"
"path/filepath"
)
// Input contains the input for the root command
type Input struct {
workingDir string
workflowsPath string
eventPath string
reuseContainers bool
dryrun bool
forcePull bool
}
func (i *Input) resolve(path string) string {
basedir, err := filepath.Abs(i.workingDir)
if err != nil {
log.Fatal(err)
}
if path == "" {
return path
}
if !filepath.IsAbs(path) {
path = filepath.Join(basedir, path)
}
return path
}
// WorkflowsPath returns path to workflows
func (i *Input) WorkflowsPath() string {
return i.resolve(i.workflowsPath)
}
// EventPath returns the path to events file
func (i *Input) EventPath() string {
return i.resolve(i.eventPath)
}

View file

@ -2,13 +2,11 @@ package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
fswatch "github.com/andreaskoch/go-fswatch"
"github.com/nektos/act/actions"
"github.com/nektos/act/common"
"github.com/nektos/act/pkg/model"
gitignore "github.com/sabhiram/go-gitignore"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@ -16,26 +14,26 @@ import (
// Execute is the entry point to running the CLI
func Execute(ctx context.Context, version string) {
runnerConfig := &actions.RunnerConfig{Ctx: ctx}
input := new(Input)
var rootCmd = &cobra.Command{
Use: "act [event name to run]",
Short: "Run Github actions locally by specifying the event name (e.g. `push`) or an action name directly.",
Args: cobra.MaximumNArgs(1),
RunE: newRunCommand(runnerConfig),
RunE: newRunCommand(ctx, input),
PersistentPreRun: setupLogging,
Version: version,
SilenceUsage: true,
}
rootCmd.Flags().BoolP("watch", "w", false, "watch the contents of the local repo and run when files change")
rootCmd.Flags().BoolP("list", "l", false, "list actions")
rootCmd.Flags().StringP("action", "a", "", "run action")
rootCmd.Flags().BoolVarP(&runnerConfig.ReuseContainers, "reuse", "r", false, "reuse action containers to maintain state")
rootCmd.Flags().StringVarP(&runnerConfig.EventPath, "event", "e", "", "path to event JSON file")
rootCmd.Flags().BoolVarP(&runnerConfig.ForcePull, "pull", "p", false, "pull docker image(s) if already present")
rootCmd.Flags().BoolP("list", "l", false, "list workflows")
rootCmd.Flags().StringP("job", "j", "", "run job")
rootCmd.Flags().BoolVarP(&input.reuseContainers, "reuse", "r", false, "reuse action containers to maintain state")
rootCmd.Flags().BoolVarP(&input.forcePull, "pull", "p", false, "pull docker image(s) if already present")
rootCmd.Flags().StringVarP(&input.eventPath, "event", "e", "", "path to event JSON file")
rootCmd.PersistentFlags().StringVarP(&input.workflowsPath, "workflows", "W", "./.github/workflows/", "path to workflow files")
rootCmd.PersistentFlags().StringVarP(&input.workingDir, "directory", "C", ".", "working directory")
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().BoolVarP(&runnerConfig.Dryrun, "dryrun", "n", false, "dryrun mode")
rootCmd.PersistentFlags().StringVarP(&runnerConfig.WorkflowPath, "file", "f", "./.github/main.workflow", "path to workflow file")
rootCmd.PersistentFlags().StringVarP(&runnerConfig.WorkingDir, "directory", "C", ".", "working directory")
rootCmd.PersistentFlags().BoolVarP(&input.dryrun, "dryrun", "n", false, "dryrun mode")
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
@ -49,67 +47,63 @@ func setupLogging(cmd *cobra.Command, args []string) {
}
}
func newRunCommand(runnerConfig *actions.RunnerConfig) func(*cobra.Command, []string) error {
func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
planner, err := model.NewWorkflowPlanner(input.WorkflowsPath())
if err != nil {
return err
}
// Determine the event name
var eventName string
if len(args) > 0 {
runnerConfig.EventName = args[0]
}
watch, err := cmd.Flags().GetBool("watch")
if err != nil {
return err
}
if watch {
return watchAndRun(runnerConfig.Ctx, func() error {
return parseAndRun(cmd, runnerConfig)
})
}
return parseAndRun(cmd, runnerConfig)
}
}
func parseAndRun(cmd *cobra.Command, runnerConfig *actions.RunnerConfig) error {
// create the runner
runner, err := actions.NewRunner(runnerConfig)
if err != nil {
return err
}
defer runner.Close()
eventName = args[0]
} else {
// set default event type if we only have a single workflow in the file.
// this way user dont have to specify the event.
if runnerConfig.EventName == "" {
if events := runner.ListEvents(); len(events) == 1 {
if events := planner.GetEvents(); len(events) == 1 {
log.Debugf("Using detected workflow event: %s", events[0])
runnerConfig.EventName = events[0]
eventName = events[0]
}
}
// fall back to default event name if we could not detect one.
if runnerConfig.EventName == "" {
runnerConfig.EventName = "push"
// build the plan for this run
var plan *model.Plan
if jobID, err := cmd.Flags().GetString("job"); err != nil {
return err
} else if jobID != "" {
log.Debugf("Planning job: %s", jobID)
plan = planner.PlanJob(jobID)
} else {
log.Debugf("Planning event: %s", eventName)
plan = planner.PlanEvent(eventName)
}
// check if we should just print the graph
list, err := cmd.Flags().GetBool("list")
if err != nil {
if list, err := cmd.Flags().GetBool("list"); err != nil {
return err
}
if list {
return drawGraph(runner)
} else if list {
return drawGraph(plan)
}
// check if we are running just a single action
actionName, err := cmd.Flags().GetString("action")
if err != nil {
return err
}
if actionName != "" {
return runner.RunActions(actionName)
}
// run the plan
// runner, err := runner.New(config)
// if err != nil {
// return err
// }
// defer runner.Close()
// run the event in the RunnerRonfig
return runner.RunEvent()
// if watch, err := cmd.Flags().GetBool("watch"); err != nil {
// return err
// } else if watch {
// return watchAndRun(ctx, func() error {
// return runner.RunPlan(plan)
// })
// }
// return runner.RunPlan(plan)
return nil
}
}
func watchAndRun(ctx context.Context, fn func() error) error {
@ -155,40 +149,3 @@ func watchAndRun(ctx context.Context, fn func() error) error {
folderWatcher.Stop()
return err
}
func drawGraph(runner actions.Runner) error {
eventNames := runner.ListEvents()
for _, eventName := range eventNames {
graph, err := runner.GraphEvent(eventName)
if err != nil {
return err
}
drawings := make([]*common.Drawing, 0)
eventPen := common.NewPen(common.StyleDoubleLine, 91 /*34*/)
drawings = append(drawings, eventPen.DrawBoxes(fmt.Sprintf("EVENT: %s", eventName)))
actionPen := common.NewPen(common.StyleSingleLine, 96)
arrowPen := common.NewPen(common.StyleNoLine, 97)
drawings = append(drawings, arrowPen.DrawArrow())
for i, stage := range graph {
if i > 0 {
drawings = append(drawings, arrowPen.DrawArrow())
}
drawings = append(drawings, actionPen.DrawBoxes(stage...))
}
maxWidth := 0
for _, d := range drawings {
if d.GetWidth() > maxWidth {
maxWidth = d.GetWidth()
}
}
for _, d := range drawings {
d.Draw(os.Stdout, maxWidth)
}
}
return nil
}