forked from tangled.org/core
this repo has no description
1package workflow 2 3import ( 4 "errors" 5 "fmt" 6 "slices" 7 8 "tangled.sh/tangled.sh/core/api/tangled" 9 10 "github.com/go-git/go-git/v5/plumbing" 11 "gopkg.in/yaml.v3" 12) 13 14// - when a repo is modified, it results in the trigger of a "Pipeline" 15// - a repo could consist of several workflow files 16// * .tangled/workflows/test.yml 17// * .tangled/workflows/lint.yml 18// - therefore a pipeline consists of several workflows, these execute in parallel 19// - each workflow consists of some execution steps, these execute serially 20 21type ( 22 Pipeline []Workflow 23 24 // this is simply a structural representation of the workflow file 25 Workflow struct { 26 Name string `yaml:"-"` // name of the workflow file 27 When []Constraint `yaml:"when"` 28 Dependencies Dependencies `yaml:"dependencies"` 29 Steps []Step `yaml:"steps"` 30 Environment map[string]string `yaml:"environment"` 31 CloneOpts CloneOpts `yaml:"clone"` 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 Dependencies map[string][]string 40 41 CloneOpts struct { 42 Skip bool `yaml:"skip"` 43 Depth int `yaml:"depth"` 44 IncludeSubmodules bool `yaml:"submodules"` 45 } 46 47 Step struct { 48 Name string `yaml:"name"` 49 Command string `yaml:"command"` 50 Environment map[string]string `yaml:"environment"` 51 } 52 53 StringList []string 54) 55 56const ( 57 TriggerKindPush string = "push" 58 TriggerKindPullRequest string = "pull_request" 59 TriggerKindManual string = "manual" 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 72 return wf, nil 73} 74 75// if any of the constraints on a workflow is true, return true 76func (w *Workflow) Match(trigger tangled.Pipeline_TriggerMetadata) bool { 77 // manual triggers always run the workflow 78 if trigger.Manual != nil { 79 return true 80 } 81 82 // if not manual, run through the constraint list and see if any one matches 83 for _, c := range w.When { 84 if c.Match(trigger) { 85 return true 86 } 87 } 88 89 // no constraints, always run this workflow 90 if len(w.When) == 0 { 91 return true 92 } 93 94 return false 95} 96 97func (c *Constraint) Match(trigger tangled.Pipeline_TriggerMetadata) bool { 98 match := true 99 100 // manual triggers always pass this constraint 101 if trigger.Manual != nil { 102 return true 103 } 104 105 // apply event constraints 106 match = match && c.MatchEvent(trigger.Kind) 107 108 // apply branch constraints for PRs 109 if trigger.PullRequest != nil { 110 match = match && c.MatchBranch(trigger.PullRequest.TargetBranch) 111 } 112 113 // apply ref constraints for pushes 114 if trigger.Push != nil { 115 match = match && c.MatchRef(trigger.Push.Ref) 116 } 117 118 return match 119} 120 121func (c *Constraint) MatchBranch(branch string) bool { 122 return slices.Contains(c.Branch, branch) 123} 124 125func (c *Constraint) MatchRef(ref string) bool { 126 refName := plumbing.ReferenceName(ref) 127 if refName.IsBranch() { 128 return slices.Contains(c.Branch, refName.Short()) 129 } 130 fmt.Println("no", c.Branch, refName.Short()) 131 132 return false 133} 134 135func (c *Constraint) MatchEvent(event string) bool { 136 return slices.Contains(c.Event, event) 137} 138 139// Custom unmarshaller for StringList 140func (s *StringList) UnmarshalYAML(unmarshal func(any) error) error { 141 var stringType string 142 if err := unmarshal(&stringType); err == nil { 143 *s = []string{stringType} 144 return nil 145 } 146 147 var sliceType []any 148 if err := unmarshal(&sliceType); err == nil { 149 150 if sliceType == nil { 151 *s = nil 152 return nil 153 } 154 155 parts := make([]string, len(sliceType)) 156 for k, v := range sliceType { 157 if sv, ok := v.(string); ok { 158 parts[k] = sv 159 } else { 160 return fmt.Errorf("cannot unmarshal '%v' of type %T into a string value", v, v) 161 } 162 } 163 164 *s = parts 165 return nil 166 } 167 168 return errors.New("failed to unmarshal StringOrSlice") 169} 170 171// conversion utilities to atproto records 172func (d Dependencies) AsRecord() []tangled.Pipeline_Dependencies_Elem { 173 var deps []tangled.Pipeline_Dependencies_Elem 174 for registry, packages := range d { 175 deps = append(deps, tangled.Pipeline_Dependencies_Elem{ 176 Registry: registry, 177 Packages: packages, 178 }) 179 } 180 return deps 181} 182 183func (s Step) AsRecord() tangled.Pipeline_Step { 184 return tangled.Pipeline_Step{ 185 Command: s.Command, 186 Name: s.Name, 187 } 188} 189 190func (c CloneOpts) AsRecord() tangled.Pipeline_CloneOpts { 191 return tangled.Pipeline_CloneOpts{ 192 Depth: int64(c.Depth), 193 Skip: c.Skip, 194 Submodules: c.IncludeSubmodules, 195 } 196}