eclipse/cmd/eclipse-trigger/main.go

230 lines
4.8 KiB
Go
Raw Normal View History

2024-01-13 02:31:32 +00:00
// Copyright (C) 2024 Umorpha Systems
// SPDX-License-Identifier: AGPL-3.0-or-later
package main
import (
"cmp"
"context"
2024-01-17 17:32:48 +00:00
"encoding/json"
2024-01-13 02:31:32 +00:00
"fmt"
"os"
"slices"
"sync"
"github.com/datawire/ocibuild/pkg/cliutil"
"github.com/spf13/cobra"
2024-01-18 05:49:09 +00:00
"git.mothstuff.lol/lukeshu/eclipse/lib/eclipse"
source "git.mothstuff.lol/lukeshu/eclipse"
2024-01-13 02:31:32 +00:00
)
func min[T cmp.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func keys[K comparable, V any](m map[K]V) []K {
ret := make([]K, 0, len(m))
for k := range m {
ret = append(ret, k)
}
return ret
}
func sortedKeys[K cmp.Ordered, V any](m map[K]V) []K {
ret := keys(m)
slices.Sort(ret)
return ret
}
2024-01-13 02:35:59 +00:00
type Set[T comparable] map[T]struct{}
func (s Set[T]) Has(v T) bool {
_, ok := s[v]
return ok
}
2024-01-13 02:31:32 +00:00
func parallel[T any](
nWorkers int,
generator func(yield func(T)),
consumer func(T),
) {
ch := make(chan T)
go func() {
defer close(ch)
generator(func(v T) { ch <- v })
}()
var wg sync.WaitGroup
wg.Add(nWorkers)
for i := 0; i < nWorkers; i++ {
go func() {
defer wg.Done()
for v := range ch {
consumer(v)
}
}()
}
wg.Wait()
}
2024-01-17 21:52:40 +00:00
func Trigger(cfgFile string, allowedRepos Set[string]) (err error) {
maybeSetErr := func(_err error) {
if err == nil && _err != nil {
err = _err
}
}
2024-01-18 05:49:09 +00:00
cfg, err := eclipse.LoadConfig(cfgFile)
2024-01-13 02:31:32 +00:00
if err != nil {
return err
}
2024-01-18 05:49:09 +00:00
actions, err := cfg.Actions()
if err != nil {
return err
}
store, err := cfg.DB()
2024-01-17 17:32:48 +00:00
if err != nil {
return err
}
2024-01-17 21:52:40 +00:00
defer func() { maybeSetErr(store.Close()) }()
2024-01-17 17:32:48 +00:00
2024-01-13 02:31:32 +00:00
repos := map[string]map[string]string{}
2024-01-18 05:49:09 +00:00
for _, action := range actions {
2024-01-13 02:31:32 +00:00
for _, trigger := range action.Triggers {
2024-01-13 02:35:59 +00:00
if len(allowedRepos) > 0 && !allowedRepos.Has(trigger.RepoURL) {
continue
}
2024-01-13 02:31:32 +00:00
if repos[trigger.RepoURL] == nil {
repos[trigger.RepoURL] = make(map[string]string, 1)
}
repos[trigger.RepoURL][trigger.Rev] = ""
}
}
2024-01-18 05:49:09 +00:00
cache := cfg.GitCache()
2024-01-13 02:31:32 +00:00
// Fetch.
2024-01-18 05:49:09 +00:00
parallel[string](min(cfg.ParallelDownloads, len(repos)),
2024-01-13 02:31:32 +00:00
func(yield func(string)) {
for repo := range repos {
yield(repo)
}
},
func(repo string) {
if err := cache.Fetch(os.Stderr, repo); err != nil {
fmt.Fprintf(os.Stderr, "error: updating %q: %v\n", repo, err)
}
},
)
// Resolve revs.
parallel[string](len(repos),
func(yield func(string)) {
for repo := range repos {
yield(repo)
}
},
func(repo string) {
exprs := keys(repos[repo])
hashs, err := cache.RevParse(os.Stderr, repo, exprs...)
if err != nil {
fmt.Fprintf(os.Stderr, "error: resolving in %q: %v\n", repo, err)
return
}
for i := range hashs {
repos[repo][exprs[i]] = hashs[i]
}
},
)
2024-01-17 17:32:48 +00:00
// Trigger.
updatedTriggers := make(map[string]string)
2024-01-18 05:49:09 +00:00
for _, action := range actions {
2024-01-17 17:32:48 +00:00
var triggerExprs []string
var triggerVals []string
for _, trigger := range action.Triggers {
newVal, ok := repos[trigger.RepoURL][trigger.Rev]
if !ok {
continue
}
triggerExprBytes, err := json.Marshal(trigger)
if err != nil {
return err
}
triggerExpr := string(triggerExprBytes)
oldVal, _ := store.GetTrigger(triggerExpr)
if newVal != oldVal {
triggerExprs = append(triggerExprs, triggerExpr)
triggerVals = append(triggerVals, newVal)
updatedTriggers[triggerExpr] = newVal
}
}
if len(triggerExprs) > 0 {
jobID, err := store.NewJob(triggerExprs, triggerVals, action.Run)
if err != nil {
return err
}
fmt.Printf("created job %v:\n", jobID)
for i := range triggerExprs {
fmt.Printf("\ttrigger: %s => %q\n", triggerExprs[i], triggerVals[i])
}
fmt.Printf("\trun: %q\n", action.Run)
}
}
for expr, val := range updatedTriggers {
fmt.Printf("remembering that %s => %q\n", expr, val)
if err := store.SetTrigger(expr, val); err != nil {
return err
2024-01-13 02:31:32 +00:00
}
}
2024-01-17 17:32:48 +00:00
2024-01-13 02:31:32 +00:00
return nil
}
func main() {
argparser := &cobra.Command{
2024-01-18 07:43:02 +00:00
Use: os.Args[0] + " [flags] [ALLOWED_REPOS...]",
2024-01-13 02:31:32 +00:00
Short: "Trigger actions",
SilenceErrors: true, // we'll handle this ourselves after .ExecuteContext()
SilenceUsage: true, // FlagErrorFunc will handle this
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
},
}
2024-01-13 02:40:04 +00:00
argparser.SetFlagErrorFunc(cliutil.FlagErrorFunc)
argparser.SetHelpTemplate(cliutil.HelpTemplate)
2024-01-13 02:31:32 +00:00
2024-01-17 21:11:23 +00:00
var cfgFile string
2024-01-18 05:49:09 +00:00
argparser.Flags().StringVar(&cfgFile, "config", source.DefaultConfigFile,
2024-01-17 21:11:23 +00:00
"Config file to use")
argparser.MarkFlagFilename("config", "yml", "yaml")
argparser.RunE = func(_ *cobra.Command, args []string) error {
allowedRepos := make(Set[string], len(args))
for _, repo := range args {
allowedRepos[repo] = struct{}{}
}
return Trigger(cfgFile, allowedRepos)
}
2024-01-13 02:31:32 +00:00
2024-01-17 21:11:23 +00:00
ctx := context.Background()
2024-01-13 02:31:32 +00:00
if err := argparser.ExecuteContext(ctx); err != nil {
fmt.Fprintf(os.Stderr, "%v: error: %v\n", argparser.CommandPath(), err)
os.Exit(1)
}
}