2020-02-04 18:38:41 -06:00
|
|
|
package model
|
|
|
|
|
|
|
|
import (
|
2020-02-07 00:17:58 -06:00
|
|
|
"fmt"
|
2020-02-04 18:38:41 -06:00
|
|
|
"io"
|
2020-02-10 18:53:14 -06:00
|
|
|
"log"
|
2020-02-10 01:03:12 -06:00
|
|
|
"regexp"
|
2020-02-07 00:17:58 -06:00
|
|
|
"strings"
|
2020-02-04 18:38:41 -06:00
|
|
|
|
2020-02-10 18:35:00 -06:00
|
|
|
"gopkg.in/yaml.v3"
|
2020-02-04 18:38:41 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
// Workflow is the structure of the files in .github/workflows
|
|
|
|
type Workflow struct {
|
2020-02-10 18:35:00 -06:00
|
|
|
Name string `yaml:"name"`
|
|
|
|
RawOn yaml.Node `yaml:"on"`
|
|
|
|
Env map[string]string `yaml:"env"`
|
|
|
|
Jobs map[string]*Job `yaml:"jobs"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// On events for the workflow
|
|
|
|
func (w *Workflow) On() []string {
|
|
|
|
|
|
|
|
switch w.RawOn.Kind {
|
|
|
|
case yaml.ScalarNode:
|
|
|
|
var val string
|
2020-02-10 18:53:14 -06:00
|
|
|
err := w.RawOn.Decode(&val)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2020-02-10 18:35:00 -06:00
|
|
|
return []string{val}
|
|
|
|
case yaml.SequenceNode:
|
|
|
|
var val []string
|
2020-02-10 18:53:14 -06:00
|
|
|
err := w.RawOn.Decode(&val)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2020-02-10 18:35:00 -06:00
|
|
|
return val
|
|
|
|
case yaml.MappingNode:
|
|
|
|
var val map[string]interface{}
|
2020-02-10 18:53:14 -06:00
|
|
|
err := w.RawOn.Decode(&val)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2020-02-10 18:35:00 -06:00
|
|
|
var keys []string
|
|
|
|
for k := range val {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
return nil
|
2020-02-04 18:38:41 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Job is the structure of one job in a workflow
|
|
|
|
type Job struct {
|
2020-02-07 00:17:58 -06:00
|
|
|
Name string `yaml:"name"`
|
2020-02-10 18:35:00 -06:00
|
|
|
RawNeeds yaml.Node `yaml:"needs"`
|
2020-02-07 00:17:58 -06:00
|
|
|
RunsOn string `yaml:"runs-on"`
|
|
|
|
Env map[string]string `yaml:"env"`
|
|
|
|
If string `yaml:"if"`
|
|
|
|
Steps []*Step `yaml:"steps"`
|
|
|
|
TimeoutMinutes int64 `yaml:"timeout-minutes"`
|
|
|
|
Container *ContainerSpec `yaml:"container"`
|
|
|
|
Services map[string]*ContainerSpec `yaml:"services"`
|
2020-02-14 02:41:20 -06:00
|
|
|
Strategy *Strategy `yaml:"strategy"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strategy for the job
|
|
|
|
type Strategy struct {
|
|
|
|
FailFast bool `yaml:"fail-fast"`
|
|
|
|
MaxParallel int `yaml:"max-parallel"`
|
|
|
|
Matrix map[string][]interface{} `yaml:"matrix"`
|
2020-02-07 00:17:58 -06:00
|
|
|
}
|
|
|
|
|
2020-02-10 18:35:00 -06:00
|
|
|
// Needs list for Job
|
|
|
|
func (j *Job) Needs() []string {
|
|
|
|
|
|
|
|
switch j.RawNeeds.Kind {
|
|
|
|
case yaml.ScalarNode:
|
|
|
|
var val string
|
2020-02-10 18:53:14 -06:00
|
|
|
err := j.RawNeeds.Decode(&val)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2020-02-10 18:35:00 -06:00
|
|
|
return []string{val}
|
|
|
|
case yaml.SequenceNode:
|
|
|
|
var val []string
|
2020-02-10 18:53:14 -06:00
|
|
|
err := j.RawNeeds.Decode(&val)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2020-02-10 18:35:00 -06:00
|
|
|
return val
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-07 00:17:58 -06:00
|
|
|
// ContainerSpec is the specification of the container to use for the job
|
|
|
|
type ContainerSpec struct {
|
|
|
|
Image string `yaml:"image"`
|
|
|
|
Env map[string]string `yaml:"env"`
|
|
|
|
Ports []int `yaml:"ports"`
|
|
|
|
Volumes []string `yaml:"volumes"`
|
|
|
|
Options string `yaml:"options"`
|
|
|
|
Entrypoint string
|
|
|
|
Args string
|
2020-02-10 17:27:05 -06:00
|
|
|
Name string
|
2020-02-04 18:38:41 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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"`
|
|
|
|
}
|
|
|
|
|
2020-02-11 11:10:35 -06:00
|
|
|
// String gets the name of step
|
|
|
|
func (s *Step) String() string {
|
|
|
|
if s.Name != "" {
|
|
|
|
return s.Name
|
|
|
|
} else if s.Uses != "" {
|
|
|
|
return s.Uses
|
|
|
|
} else if s.Run != "" {
|
|
|
|
return s.Run
|
|
|
|
}
|
|
|
|
return s.ID
|
|
|
|
}
|
|
|
|
|
2020-02-07 00:17:58 -06:00
|
|
|
// GetEnv gets the env for a step
|
|
|
|
func (s *Step) GetEnv() map[string]string {
|
|
|
|
rtnEnv := make(map[string]string)
|
|
|
|
for k, v := range s.Env {
|
|
|
|
rtnEnv[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range s.With {
|
2020-02-10 17:27:05 -06:00
|
|
|
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(k), "_")
|
|
|
|
envKey = fmt.Sprintf("INPUT_%s", strings.ToUpper(envKey))
|
2020-02-07 00:17:58 -06:00
|
|
|
rtnEnv[envKey] = v
|
|
|
|
}
|
|
|
|
return rtnEnv
|
|
|
|
}
|
|
|
|
|
2020-02-10 01:03:12 -06:00
|
|
|
// StepType describes what type of step we are about to run
|
|
|
|
type StepType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// StepTypeRun is all steps that have a `run` attribute
|
|
|
|
StepTypeRun StepType = iota
|
|
|
|
|
|
|
|
//StepTypeUsesDockerURL is all steps that have a `uses` that is of the form `docker://...`
|
|
|
|
StepTypeUsesDockerURL
|
|
|
|
|
|
|
|
//StepTypeUsesActionLocal is all steps that have a `uses` that is a reference to a github repo
|
|
|
|
StepTypeUsesActionLocal
|
|
|
|
|
|
|
|
//StepTypeUsesActionRemote is all steps that have a `uses` that is a local action in a subdirectory
|
|
|
|
StepTypeUsesActionRemote
|
|
|
|
)
|
|
|
|
|
|
|
|
// Type returns the type of the step
|
|
|
|
func (s *Step) Type() StepType {
|
|
|
|
if s.Run != "" {
|
|
|
|
return StepTypeRun
|
|
|
|
} else if strings.HasPrefix(s.Uses, "docker://") {
|
|
|
|
return StepTypeUsesDockerURL
|
|
|
|
} else if strings.HasPrefix(s.Uses, "./") {
|
|
|
|
return StepTypeUsesActionLocal
|
|
|
|
}
|
|
|
|
return StepTypeUsesActionRemote
|
|
|
|
}
|
|
|
|
|
2020-02-04 18:38:41 -06:00
|
|
|
// 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
|
|
|
|
}
|