forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
1package workflow
2
3import (
4 "errors"
5 "fmt"
6 "slices"
7 "strings"
8
9 "tangled.org/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}