package actions import ( "fmt" "log" "os" "github.com/howeyc/gopass" ) type workflowModel struct { On string Resolves []string } type actionModel struct { Needs []string Uses string Runs []string Args []string Env map[string]string Secrets []string } type workflowsFile struct { Workflow map[string]workflowModel Action map[string]actionModel } func (wFile *workflowsFile) getWorkflow(eventName string) (*workflowModel, string, error) { var rtn workflowModel for wName, w := range wFile.Workflow { if w.On == eventName { rtn = w return &rtn, wName, nil } } return nil, "", fmt.Errorf("unsupported event: %v", eventName) } func (wFile *workflowsFile) getAction(actionName string) (*actionModel, error) { if a, ok := wFile.Action[actionName]; ok { return &a, nil } return nil, fmt.Errorf("unsupported action: %v", actionName) } // return a pipeline that is run in series. pipeline is a list of steps to run in parallel func (wFile *workflowsFile) newExecutionGraph(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 { actionDependencies[aName] = wFile.Action[aName].Needs newActionNames = append(newActionNames, wFile.Action[aName].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 } var secretCache map[string]string func (action *actionModel) applyEnvironment(env map[string]string) { for envKey, envValue := range action.Env { env[envKey] = envValue } for _, secret := range action.Secrets { if secretVal, ok := os.LookupEnv(secret); ok { env[secret] = secretVal } else { if secretCache == nil { secretCache = make(map[string]string) } if _, ok := secretCache[secret]; !ok { fmt.Printf("Provide value for '%s': ", secret) val, err := gopass.GetPasswdMasked() if err != nil { log.Fatal("abort") } secretCache[secret] = string(val) } env[secret] = secretCache[secret] } } }