repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
services/luci/internal/ciconfig/match.go
Rawpackage ciconfig
import (
"fmt"
"sort"
"strings"
"github.com/bmatcuk/doublestar/v4"
"bugabinga.net/luci/internal/storage"
)
const MaxMatrixChildren = 64
func (cfg Config) Job(name string) (Job, bool) {
for _, job := range cfg.Jobs {
if job.Name == name {
return job, true
}
}
return Job{}, false
}
func (job Job) NeedsChangedPaths() bool {
return job.Trigger.Push != nil && (len(job.Trigger.Push.Include) > 0 || len(job.Trigger.Push.Exclude) > 0)
}
func (job Job) MatchesPush(ref string, changedPaths []string) bool {
push := job.Trigger.Push
if push == nil {
return false
}
if branch, ok := strings.CutPrefix(ref, "refs/heads/"); ok {
if len(push.Branches) == 0 && len(push.Tags) > 0 {
return false
}
if !matchesAny(push.Branches, branch) {
return false
}
} else if tag, ok := strings.CutPrefix(ref, "refs/tags/"); ok {
if len(push.Tags) == 0 && len(push.Branches) > 0 {
return false
}
if !matchesAny(push.Tags, tag) {
return false
}
} else {
return false
}
if len(push.Include) == 0 && len(push.Exclude) == 0 {
return true
}
for _, changed := range changedPaths {
if matchesAny(push.Include, changed) && !matchesAnyNonempty(push.Exclude, changed) {
return true
}
}
return false
}
func matchesAny(patterns []string, value string) bool {
if len(patterns) == 0 {
return true
}
return matchesAnyNonempty(patterns, value)
}
func matchesAnyNonempty(patterns []string, value string) bool {
for _, pattern := range patterns {
matched, _ := doublestar.Match(pattern, value)
if matched {
return true
}
}
return false
}
func validateGlobs(patterns []string) error {
for _, pattern := range patterns {
if strings.TrimSpace(pattern) == "" || !doublestar.ValidatePattern(pattern) {
return fmt.Errorf("invalid glob %q", pattern)
}
}
return nil
}
func (job Job) MatrixSize() int {
size := 1
for _, axis := range job.Matrix.Axes {
if len(axis.Values) == 0 || size > MaxMatrixChildren/len(axis.Values) {
return MaxMatrixChildren + 1
}
size *= len(axis.Values)
}
return size
}
func (job Job) ExpandMatrix() []ExpandedJob {
if len(job.Matrix.Axes) == 0 {
return []ExpandedJob{{Job: job, Name: job.Name, Values: map[string]string{}}}
}
var out []ExpandedJob
var walk func(int, map[string]string)
walk = func(axisIndex int, values map[string]string) {
if axisIndex == len(job.Matrix.Axes) {
copyValues := make(map[string]string, len(values))
for key, value := range values {
copyValues[key] = value
}
out = append(out, ExpandedJob{Job: job, Name: matrixName(job.Name, job.Matrix.Axes, copyValues), Values: copyValues})
return
}
axis := job.Matrix.Axes[axisIndex]
for _, value := range axis.Values {
values[axis.Name] = value
walk(axisIndex+1, values)
}
delete(values, axis.Name)
}
walk(0, map[string]string{})
return out
}
func matrixName(name string, axes []Axis, values map[string]string) string {
parts := make([]string, 0, len(axes))
for _, axis := range axes {
parts = append(parts, axis.Name+"="+values[axis.Name])
}
return name + "[" + strings.Join(parts, ",") + "]"
}
// Interpolate resolves matrix placeholders exactly as execution does.
func Interpolate(text string, values map[string]string) string {
keys := make([]string, 0, len(values))
for key := range values {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
text = strings.ReplaceAll(text, "${"+key+"}", values[key])
}
return text
}
func (job Job) SecretPaths(dataDir string, repo string) (map[string]string, error) {
layout := storage.New(dataDir)
paths := make(map[string]string, len(job.Secrets))
for _, secret := range job.Secrets {
path, err := layout.SecretPath(repo, job.Name, secret.Name)
if err != nil {
return nil, err
}
paths[secret.Name] = path
}
return paths, nil
}