-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathworkflows.go
231 lines (177 loc) · 4.72 KB
/
workflows.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package main
import (
"context"
"fmt"
"path/filepath"
"strconv"
"strings"
"gopkg.in/yaml.v3"
"github.com/aweris/gale/common/model"
)
type Workflows struct {
Repo *RepoInfo
WorkflowsDir string
}
// List returns a list of workflows and their jobs with the given options.
func (w *Workflows) List(ctx context.Context) ([]Workflow, error) {
var workflows []Workflow
wd := w.Repo.Source.Directory(w.WorkflowsDir)
entries, err := wd.Entries(ctx)
if err != nil {
return nil, err
}
for _, entry := range entries {
// Process only .yaml and .yml files
if strings.HasSuffix(entry, ".yaml") || strings.HasSuffix(entry, ".yml") {
workflow, err := w.loadWorkflow(ctx, filepath.Join(w.WorkflowsDir, entry), wd.File(entry))
if err != nil {
return nil, err
}
workflows = append(workflows, *workflow)
}
}
return workflows, nil
}
// Get returns a workflow.
func (w *Workflows) Get(ctx context.Context, workflow string) (*Workflow, error) {
workflows, err := w.List(ctx)
if err != nil {
return nil, err
}
for _, wf := range workflows {
if wf.Name == workflow || wf.Path == workflow {
return &wf, nil
}
}
return nil, fmt.Errorf("workflow %s not found", workflow)
}
type Workflow struct {
// Relative path of the workflow file.
Path string
// Workflow file source.
Src *File
// Path to the workflow. For example, octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch.
Ref string
// WorkflowSHA is the commit SHA for the workflow file.
SHA string
// Workflow name. Defaults to the file path.
Name string
// Environment variables used in the workflow. Format: KEY=VALUE.
Env []KV
// Jobs in the workflow.
Jobs []Job
}
func (w *Workflows) loadWorkflow(ctx context.Context, path string, workflow *File) (*Workflow, error) {
var wm model.Workflow
data, err := workflow.Contents(ctx)
if err != nil {
return nil, err
}
// TODO: refactor this when dagger supports map types. This is a temporary workaround.
if err := yaml.Unmarshal([]byte(data), &wm); err != nil {
return nil, err
}
// update workflow name with path if not provided
if wm.Name == "" {
wm.Name = path
}
jobs := make([]Job, 0, len(wm.Jobs))
for id, job := range wm.Jobs {
jobs = append(jobs, loadJob(id, job))
}
ref := ""
// if path is empty, it means we're loading external workflow file which is not in the repository source.
// In this case, we don't need to set the ref.
if path != "" {
ref = fmt.Sprintf("%s/%s@%s", w.Repo.NameWithOwner, path, w.Repo.Ref)
}
return &Workflow{
Path: path,
Src: workflow,
Ref: ref,
SHA: w.Repo.SHA,
Name: wm.Name,
Env: ConvertMapToKVSlice(wm.Env),
Jobs: jobs,
}, nil
}
// Returns the YAML representation of the workflow.
func (w *Workflow) Yaml(ctx context.Context) (string, error) {
return w.Src.Contents(ctx)
}
type Job struct {
// ID of the job.
JobID string
// Name of the job.
Name string
// Conditional expression to run the job.
Condition string
// List of jobs that must be completed before this job will run.
Needs []string
// Environment variables used in the job. Format: KEY=VALUE.
Env []KV
// List of outputs of the job.
Outputs []string
// List of steps in the job.
Steps []Step
}
func loadJob(id string, jm model.Job) Job {
steps := make([]Step, len(jm.Steps))
for i, step := range jm.Steps {
steps[i] = loadStep(step)
if steps[i].StepID == "" {
steps[i].StepID = strconv.Itoa(i)
}
}
name := jm.Name
if name == "" {
name = id
}
return Job{
JobID: id,
Name: name,
Condition: jm.If,
Needs: jm.Needs,
Env: ConvertMapToKVSlice(jm.Env),
Steps: steps,
}
}
type Step struct {
// Unique identifier of the step. Defaults to the step index in the job.
StepID string
// Conditional expression to run the step.
Condition string
// Name of the step.
Name string
// Action to run for the step.
Uses string
// Environment variables used in the step. Format: KEY=VALUE.
Env []KV
// Inputs used in the step. Format: KEY=VALUE.
With []KV
// Command to run for the step.
Run string
// Shell to use for the step.
Shell string
// Working directory for the step.
WorkingDirectory string
// Flag to continue on error.
ContinueOnError bool
// Maximum number of minutes to run the step.
TimeoutMinutes int
}
func loadStep(step model.Step) Step {
return Step{
StepID: step.ID,
Condition: step.If,
Name: step.Name,
Uses: step.Uses,
Env: ConvertMapToKVSlice(step.Environment),
With: ConvertMapToKVSlice(step.With),
Run: step.Run,
Shell: step.Shell,
WorkingDirectory: step.WorkingDirectory,
ContinueOnError: step.ContinueOnError,
TimeoutMinutes: step.TimeoutMinutes,
}
}