forked from tangled.org/core
this repo has no description
at master 4.0 kB view raw
1package workflow 2 3import ( 4 "errors" 5 "fmt" 6 "slices" 7 "strings" 8 9 "tangled.sh/tangled.sh/core/api/tangled" 10 11 "github.com/go-git/go-git/v5/plumbing" 12 "gopkg.in/yaml.v3" 13) 14 15// - when a repo is modified, it results in the trigger of a "Pipeline" 16// - a repo could consist of several workflow files 17// * .tangled/workflows/test.yml 18// * .tangled/workflows/lint.yml 19// - therefore a pipeline consists of several workflows, these execute in parallel 20// - each workflow consists of some execution steps, these execute serially 21 22type ( 23 Pipeline []Workflow 24 25 // this is simply a structural representation of the workflow file 26 Workflow struct { 27 Name string `yaml:"-"` // name of the workflow file 28 Engine string `yaml:"engine"` 29 When []Constraint `yaml:"when"` 30 CloneOpts CloneOpts `yaml:"clone"` 31 Raw string `yaml:"-"` 32 } 33 34 Constraint struct { 35 Event StringList `yaml:"event"` 36 Branch StringList `yaml:"branch"` // this is optional, and only applied on "push" events 37 } 38 39 CloneOpts struct { 40 Skip bool `yaml:"skip"` 41 Depth int `yaml:"depth"` 42 IncludeSubmodules bool `yaml:"submodules"` 43 } 44 45 StringList []string 46 47 TriggerKind string 48) 49 50const ( 51 WorkflowDir = ".tangled/workflows" 52 53 TriggerKindPush TriggerKind = "push" 54 TriggerKindPullRequest TriggerKind = "pull_request" 55 TriggerKindManual TriggerKind = "manual" 56) 57 58func (t TriggerKind) String() string { 59 return strings.ReplaceAll(string(t), "_", " ") 60} 61 62func FromFile(name string, contents []byte) (Workflow, error) { 63 var wf Workflow 64 65 err := yaml.Unmarshal(contents, &wf) 66 if err != nil { 67 return wf, err 68 } 69 70 wf.Name = name 71 wf.Raw = string(contents) 72 73 return wf, nil 74} 75 76// if any of the constraints on a workflow is true, return true 77func (w *Workflow) Match(trigger tangled.Pipeline_TriggerMetadata) bool { 78 // manual triggers always run the workflow 79 if trigger.Manual != nil { 80 return true 81 } 82 83 // if not manual, run through the constraint list and see if any one matches 84 for _, c := range w.When { 85 if c.Match(trigger) { 86 return true 87 } 88 } 89 90 // no constraints, always run this workflow 91 if len(w.When) == 0 { 92 return true 93 } 94 95 return false 96} 97 98func (c *Constraint) Match(trigger tangled.Pipeline_TriggerMetadata) bool { 99 match := true 100 101 // manual triggers always pass this constraint 102 if trigger.Manual != nil { 103 return true 104 } 105 106 // apply event constraints 107 match = match && c.MatchEvent(trigger.Kind) 108 109 // apply branch constraints for PRs 110 if trigger.PullRequest != nil { 111 match = match && c.MatchBranch(trigger.PullRequest.TargetBranch) 112 } 113 114 // apply ref constraints for pushes 115 if trigger.Push != nil { 116 match = match && c.MatchRef(trigger.Push.Ref) 117 } 118 119 return match 120} 121 122func (c *Constraint) MatchBranch(branch string) bool { 123 return slices.Contains(c.Branch, branch) 124} 125 126func (c *Constraint) MatchRef(ref string) bool { 127 refName := plumbing.ReferenceName(ref) 128 if refName.IsBranch() { 129 return slices.Contains(c.Branch, refName.Short()) 130 } 131 return false 132} 133 134func (c *Constraint) MatchEvent(event string) bool { 135 return slices.Contains(c.Event, event) 136} 137 138// Custom unmarshaller for StringList 139func (s *StringList) UnmarshalYAML(unmarshal func(any) error) error { 140 var stringType string 141 if err := unmarshal(&stringType); err == nil { 142 *s = []string{stringType} 143 return nil 144 } 145 146 var sliceType []any 147 if err := unmarshal(&sliceType); err == nil { 148 149 if sliceType == nil { 150 *s = nil 151 return nil 152 } 153 154 parts := make([]string, len(sliceType)) 155 for k, v := range sliceType { 156 if sv, ok := v.(string); ok { 157 parts[k] = sv 158 } else { 159 return fmt.Errorf("cannot unmarshal '%v' of type %T into a string value", v, v) 160 } 161 } 162 163 *s = parts 164 return nil 165 } 166 167 return errors.New("failed to unmarshal StringOrSlice") 168} 169 170func (c CloneOpts) AsRecord() tangled.Pipeline_CloneOpts { 171 return tangled.Pipeline_CloneOpts{ 172 Depth: int64(c.Depth), 173 Skip: c.Skip, 174 Submodules: c.IncludeSubmodules, 175 } 176}