This repository has no description
1package workflow
2
3import (
4 "errors"
5 "fmt"
6
7 "tangled.org/core/api/tangled"
8)
9
10type RawWorkflow struct {
11 Name string
12 Contents []byte
13}
14
15type RawPipeline = []RawWorkflow
16
17type Compiler struct {
18 Trigger tangled.Pipeline_TriggerMetadata
19 ChangedFiles []string
20 Diagnostics Diagnostics
21}
22
23type Diagnostics struct {
24 Errors []Error
25 Warnings []Warning
26}
27
28func (d *Diagnostics) IsEmpty() bool {
29 return len(d.Errors) == 0 && len(d.Warnings) == 0
30}
31
32func (d *Diagnostics) Combine(o Diagnostics) {
33 d.Errors = append(d.Errors, o.Errors...)
34 d.Warnings = append(d.Warnings, o.Warnings...)
35}
36
37func (d *Diagnostics) AddWarning(path string, kind WarningKind, reason string) {
38 d.Warnings = append(d.Warnings, Warning{path, kind, reason})
39}
40
41func (d *Diagnostics) AddError(path string, err error) {
42 d.Errors = append(d.Errors, Error{path, err})
43}
44
45func (d Diagnostics) IsErr() bool {
46 return len(d.Errors) != 0
47}
48
49type Error struct {
50 Path string
51 Error error
52}
53
54func (e Error) String() string {
55 return fmt.Sprintf("error: %s: %s", e.Path, e.Error.Error())
56}
57
58type Warning struct {
59 Path string
60 Type WarningKind
61 Reason string
62}
63
64func (w Warning) String() string {
65 return fmt.Sprintf("warning: %s: %s: %s", w.Path, w.Type, w.Reason)
66}
67
68var (
69 MissingEngine error = errors.New("missing engine")
70)
71
72type WarningKind string
73
74var (
75 WorkflowSkipped WarningKind = "workflow skipped"
76 InvalidConfiguration WarningKind = "invalid configuration"
77)
78
79func (compiler *Compiler) Parse(p RawPipeline) Pipeline {
80 var pp Pipeline
81
82 for _, w := range p {
83 wf, err := FromFile(w.Name, w.Contents)
84 if err != nil {
85 compiler.Diagnostics.AddError(w.Name, err)
86 continue
87 }
88
89 pp = append(pp, wf)
90 }
91
92 return pp
93}
94
95// convert a repositories' workflow files into a fully compiled pipeline that runners accept
96func (compiler *Compiler) Compile(p Pipeline) tangled.Pipeline {
97 cp := tangled.Pipeline{
98 TriggerMetadata: &compiler.Trigger,
99 }
100
101 for _, wf := range p {
102 cw := compiler.compileWorkflow(wf)
103
104 if cw == nil {
105 continue
106 }
107
108 cp.Workflows = append(cp.Workflows, cw)
109 }
110
111 return cp
112}
113
114func (compiler *Compiler) compileWorkflow(w Workflow) *tangled.Pipeline_Workflow {
115 matched, err := w.Match(compiler.Trigger, compiler.ChangedFiles)
116 if err != nil {
117 compiler.Diagnostics.AddError(
118 w.Name,
119 fmt.Errorf("failed to execute workflow: %w", err),
120 )
121 return nil
122 }
123 if !matched {
124 compiler.Diagnostics.AddWarning(
125 w.Name,
126 WorkflowSkipped,
127 fmt.Sprintf("did not match trigger %s", compiler.Trigger.Kind),
128 )
129 return nil
130 }
131
132 // validate clone options
133 compiler.analyzeCloneOptions(w)
134
135 if w.Engine == "" {
136 compiler.Diagnostics.AddError(w.Name, MissingEngine)
137 return nil
138 }
139
140 o := w.CloneOpts.AsRecord()
141 cw := &tangled.Pipeline_Workflow{
142 Clone: &o,
143 Engine: w.Engine,
144 Name: w.Name,
145 Raw: w.Raw,
146 RunsOn: w.RunsOn,
147 }
148
149 return cw
150}
151
152func (compiler *Compiler) analyzeCloneOptions(w Workflow) {
153 if !w.CloneOpts.Skip {
154 return
155 }
156
157 warn := func(key string) {
158 compiler.Diagnostics.AddWarning(
159 w.Name,
160 InvalidConfiguration,
161 fmt.Sprintf("cannot apply `clone.skip` and `clone.%s`", key),
162 )
163 }
164 if w.CloneOpts.Tags != nil {
165 warn("tags")
166 }
167 if w.CloneOpts.IncludeSubmodules != nil {
168 warn("submodules")
169 }
170 if w.CloneOpts.Depth > 0 {
171 warn("depth")
172 }
173}