package workflow
import (
"errors"
"fmt"
)
type RawWorkflow struct {
Name string
Contents []byte
}
type RawPipeline = []RawWorkflow
type Compiler struct {
Trigger TriggerMetadata
ChangedFiles []string
Diagnostics Diagnostics
}
type Diagnostics struct {
Errors []Error
Warnings []Warning
}
func (d *Diagnostics) IsEmpty() bool {
return len(d.Errors) == 0 && len(d.Warnings) == 0
}
func (d *Diagnostics) Combine(o Diagnostics) {
d.Errors = append(d.Errors, o.Errors...)
d.Warnings = append(d.Warnings, o.Warnings...)
}
func (d *Diagnostics) AddWarning(path string, kind WarningKind, reason string) {
d.Warnings = append(d.Warnings, Warning{path, kind, reason})
}
func (d *Diagnostics) AddError(path string, err error) {
d.Errors = append(d.Errors, Error{path, err})
}
func (d Diagnostics) IsErr() bool {
return len(d.Errors) != 0
}
type Error struct {
Path string
Error error
}
func (e Error) String() string {
return fmt.Sprintf("error: %s: %s", e.Path, e.Error.Error())
}
type Warning struct {
Path string
Type WarningKind
Reason string
}
func (w Warning) String() string {
return fmt.Sprintf("warning: %s: %s: %s", w.Path, w.Type, w.Reason)
}
var (
MissingEngine error = errors.New("missing engine")
)
type WarningKind string
var (
WorkflowSkipped WarningKind = "workflow skipped"
InvalidConfiguration WarningKind = "invalid configuration"
)
func (compiler *Compiler) Parse(p RawPipeline) Pipeline {
var pp Pipeline
for _, w := range p {
wf, err := FromFile(w.Name, w.Contents)
if err != nil {
compiler.Diagnostics.AddError(w.Name, err)
continue
}
pp = append(pp, wf)
}
return pp
}
// convert a repositories' workflow files into a fully compiled pipeline that runners accept
func (compiler *Compiler) Compile(p Pipeline) PipelineSpec {
cp := PipelineSpec{
TriggerMetadata: &compiler.Trigger,
}
for _, wf := range p {
cw := compiler.compileWorkflow(wf)
if cw == nil {
continue
}
cp.Workflows = append(cp.Workflows, cw)
}
return cp
}
func (compiler *Compiler) compileWorkflow(w Workflow) *WorkflowSpec {
matched, err := w.Match(compiler.Trigger, compiler.ChangedFiles)
if err != nil {
compiler.Diagnostics.AddError(
w.Name,
fmt.Errorf("failed to execute workflow: %w", err),
)
return nil
}
if !matched {
compiler.Diagnostics.AddWarning(
w.Name,
WorkflowSkipped,
fmt.Sprintf("did not match trigger %s", compiler.Trigger.Kind),
)
return nil
}
// validate clone options
compiler.analyzeCloneOptions(w)
if w.Engine == "" {
compiler.Diagnostics.AddError(w.Name, MissingEngine)
return nil
}
o := w.CloneOpts.AsRecord()
cw := &WorkflowSpec{
Clone: &o,
Engine: w.Engine,
Name: w.Name,
Raw: w.Raw,
RunsOn: w.RunsOn,
}
if w.Audience != "" {
cw.Audience = &w.Audience
}
return cw
}
func (compiler *Compiler) analyzeCloneOptions(w Workflow) {
if !w.CloneOpts.Skip {
return
}
warn := func(key string) {
compiler.Diagnostics.AddWarning(
w.Name,
InvalidConfiguration,
fmt.Sprintf("cannot apply `clone.skip` and `clone.%s`", key),
)
}
if w.CloneOpts.Tags != nil {
warn("tags")
}
if w.CloneOpts.IncludeSubmodules != nil {
warn("submodules")
}
if w.CloneOpts.Depth > 0 {
warn("depth")
}
}