diff --git a/cmd/actions/build.go b/cmd/actions/build.go index d54e9e0..305a35a 100644 --- a/cmd/actions/build.go +++ b/cmd/actions/build.go @@ -22,12 +22,12 @@ var buildCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, "", flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } diff --git a/cmd/actions/config.go b/cmd/actions/config.go index 7abb841..5010f96 100644 --- a/cmd/actions/config.go +++ b/cmd/actions/config.go @@ -22,12 +22,12 @@ var configCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, "", flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } diff --git a/cmd/actions/diff.go b/cmd/actions/diff.go index 96c513d..68d1891 100644 --- a/cmd/actions/diff.go +++ b/cmd/actions/diff.go @@ -19,14 +19,14 @@ var diffCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, flagNamespace, flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } args, helmArgs := parseExtraArgs(args) squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } diff --git a/cmd/actions/down.go b/cmd/actions/down.go index 651931d..3522a66 100644 --- a/cmd/actions/down.go +++ b/cmd/actions/down.go @@ -21,14 +21,14 @@ var downCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, flagNamespace, flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } args, helmArgs := parseExtraArgs(args) squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } diff --git a/cmd/actions/list.go b/cmd/actions/list.go index 5434fba..17f5fa7 100644 --- a/cmd/actions/list.go +++ b/cmd/actions/list.go @@ -1,6 +1,8 @@ package actions import ( + "context" + "github.com/foomo/squadron" "github.com/foomo/squadron/internal/config" "github.com/pkg/errors" @@ -30,21 +32,21 @@ var listCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, "", flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } var list pterm.LeveledList // List squadrons - _ = sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { + _ = sq.Config().Squadrons.Iterate(cmd.Context(), func(ctx context.Context, key string, value config.Map[*config.Unit]) error { list = append(list, pterm.LeveledListItem{Level: 0, Text: key}) - return value.Iterate(func(k string, v *config.Unit) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { list = append(list, pterm.LeveledListItem{Level: 1, Text: k}) if flagWithTags && len(v.Tags) > 0 { list = append(list, pterm.LeveledListItem{Level: 2, Text: "šŸ”–: " + v.Tags.SortedString()}) diff --git a/cmd/actions/push.go b/cmd/actions/push.go index e037c2c..66d04ec 100644 --- a/cmd/actions/push.go +++ b/cmd/actions/push.go @@ -22,12 +22,12 @@ var pushCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, flagNamespace, flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } diff --git a/cmd/actions/rollback.go b/cmd/actions/rollback.go index 70d7c15..6022aa9 100644 --- a/cmd/actions/rollback.go +++ b/cmd/actions/rollback.go @@ -21,14 +21,14 @@ var rollbackCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, flagNamespace, flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } args, helmArgs := parseExtraArgs(args) squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } diff --git a/cmd/actions/root.go b/cmd/actions/root.go index 6429ccb..9701e1d 100644 --- a/cmd/actions/root.go +++ b/cmd/actions/root.go @@ -58,9 +58,12 @@ func init() { rootCmd.AddCommand(upCmd, diffCmd, downCmd, buildCmd, pushCmd, listCmd, rollbackCmd, statusCmd, configCmd, versionCmd, completionCmd, templateCmd, postRendererCmd) - pterm.Info = *pterm.Info.WithPrefix(pterm.Prefix{Text: "INFO", Style: pterm.Info.Prefix.Style}) - pterm.Error = *pterm.Info.WithPrefix(pterm.Prefix{Text: "ERROR", Style: pterm.Error.Prefix.Style}) - pterm.Warning = *pterm.Info.WithPrefix(pterm.Prefix{Text: "WARNING", Style: pterm.Warning.Prefix.Style}) + pterm.Info = *pterm.Info.WithPrefix(pterm.Prefix{Text: "āŽˆ", Style: pterm.Info.Prefix.Style}) + pterm.Debug = *pterm.Debug.WithPrefix(pterm.Prefix{Text: "āš’ļøŽ", Style: pterm.Debug.Prefix.Style}) + pterm.Fatal = *pterm.Fatal.WithPrefix(pterm.Prefix{Text: "šŸ’€", Style: pterm.Fatal.Prefix.Style}) + pterm.Error = *pterm.Error.WithPrefix(pterm.Prefix{Text: "ā›Œ", Style: pterm.Error.Prefix.Style}) + pterm.Warning = *pterm.Info.WithPrefix(pterm.Prefix{Text: "⚠", Style: pterm.Warning.Prefix.Style}) + pterm.Success = *pterm.Success.WithPrefix(pterm.Prefix{Text: "āœ“", Style: pterm.Success.Prefix.Style}) } func Execute() { diff --git a/cmd/actions/status.go b/cmd/actions/status.go index d05809a..92c4f53 100644 --- a/cmd/actions/status.go +++ b/cmd/actions/status.go @@ -20,14 +20,14 @@ var statusCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, flagNamespace, flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } args, helmArgs := parseExtraArgs(args) squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } diff --git a/cmd/actions/template.go b/cmd/actions/template.go index b0c0f28..c931d53 100644 --- a/cmd/actions/template.go +++ b/cmd/actions/template.go @@ -23,14 +23,14 @@ var templateCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, flagNamespace, flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } args, helmArgs := parseExtraArgs(args) squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } diff --git a/cmd/actions/up.go b/cmd/actions/up.go index 82c4215..a9ef71b 100644 --- a/cmd/actions/up.go +++ b/cmd/actions/up.go @@ -27,14 +27,14 @@ var upCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { sq := squadron.New(cwd, flagNamespace, flagFiles) - if err := sq.MergeConfigFiles(); err != nil { + if err := sq.MergeConfigFiles(cmd.Context()); err != nil { return errors.Wrap(err, "failed to merge config files") } args, helmArgs := parseExtraArgs(args) squadronName, unitNames := parseSquadronAndUnitNames(args) - if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil { + if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, flagTags); err != nil { return errors.Wrap(err, "failed to filter config") } diff --git a/internal/config/config.go b/internal/config/config.go index 83d5c62..fe8c871 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,6 +1,7 @@ package config import ( + "context" "fmt" "github.com/pkg/errors" @@ -21,10 +22,10 @@ type Config struct { } // BuildDependencies returns a map of requested build dependencies -func (c *Config) BuildDependencies() map[string]Build { +func (c *Config) BuildDependencies(ctx context.Context) map[string]Build { ret := map[string]Build{} - _ = c.Squadrons.Iterate(func(key string, value Map[*Unit]) error { - return value.Iterate(func(k string, v *Unit) error { + _ = c.Squadrons.Iterate(ctx, func(ctx context.Context, key string, value Map[*Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *Unit) error { for _, build := range v.Builds { for _, dependency := range build.Dependencies { b, ok := c.Builds[dependency] @@ -44,8 +45,8 @@ func (c *Config) BuildDependencies() map[string]Build { } // Trim delete empty squadron recursively -func (c *Config) Trim() { - _ = c.Squadrons.Iterate(func(key string, value Map[*Unit]) error { +func (c *Config) Trim(ctx context.Context) { + _ = c.Squadrons.Iterate(ctx, func(ctx context.Context, key string, value Map[*Unit]) error { value.Trim() return nil }) diff --git a/internal/config/map.go b/internal/config/map.go index 9b0c0e5..053e83e 100644 --- a/internal/config/map.go +++ b/internal/config/map.go @@ -1,6 +1,7 @@ package config import ( + "context" "reflect" "slices" "sort" @@ -89,12 +90,15 @@ func (m Map[T]) FilterFn(handler func(key string, value T) bool) error { return nil } -func (m Map[T]) Iterate(handler func(key string, value T) error) error { +func (m Map[T]) Iterate(ctx context.Context, handler func(ctx context.Context, key string, value T) error) error { if len(m) == 0 { return nil } for _, key := range m.Keys() { - if err := handler(key, m[key]); err != nil { + if err := ctx.Err(); err != nil { + return err + } + if err := handler(ctx, key, m[key]); err != nil { return err } } diff --git a/internal/config/unit.go b/internal/config/unit.go index 1a4816c..ac65ea5 100644 --- a/internal/config/unit.go +++ b/internal/config/unit.go @@ -5,12 +5,12 @@ import ( "context" "fmt" "path" + "sort" "strings" "github.com/foomo/squadron/internal/helm" "github.com/foomo/squadron/internal/util" "github.com/pkg/errors" - "github.com/pterm/pterm" yamlv2 "gopkg.in/yaml.v2" ) @@ -44,34 +44,13 @@ func (u *Unit) ValuesYAML(global, vars map[string]any) ([]byte, error) { return yamlv2.Marshal(values) } -func (u *Unit) Build(ctx context.Context, squadron, unit string, args []string) (string, error) { - var i int - for _, build := range u.Builds { - i++ - pterm.Info.Printfln("[%d/%d] Building %s/%s", i, len(u.Builds), squadron, unit) - pterm.FgGray.Printfln("ā”” %s:%s", build.Image, build.Tag) - if out, err := build.Build(ctx, args); err != nil { - pterm.Error.Printfln("[%d/%d] Failed to build squadron unit %s/%s", i, len(u.Builds), squadron, unit) - pterm.FgGray.Printfln("ā”” %s:%s", build.Image, build.Tag) - return out, err - } +func (u *Unit) BuildNames() []string { + ret := make([]string, 0, len(u.Builds)) + for name := range u.Builds { + ret = append(ret, name) } - return "", nil -} - -func (u *Unit) Push(ctx context.Context, squadron, unit string, args []string) (string, error) { - var i int - for _, build := range u.Builds { - i++ - pterm.Info.Printfln("[%d/%d] Pushing %s/%s", i, len(u.Builds), squadron, unit) - pterm.FgGray.Printfln("ā”” %s:%s", build.Image, build.Tag) - if out, err := build.Push(ctx, args); err != nil { - pterm.Error.Printfln("[%d/%d] Failed to push %s/%s", i, len(u.Builds), squadron, unit) - pterm.FgGray.Printfln("ā”” %s:%s", build.Image, build.Tag) - return out, err - } - } - return "", nil + sort.Strings(ret) + return ret } func (u *Unit) Template(ctx context.Context, name, squadron, unit, namespace string, global, vars map[string]any, helmArgs []string) ([]byte, error) { diff --git a/internal/template/format.go b/internal/template/format.go index be09382..ae8f749 100644 --- a/internal/template/format.go +++ b/internal/template/format.go @@ -1,9 +1,30 @@ package template import ( + "fmt" "strings" ) +func quote(str ...any) string { + out := make([]string, 0, len(str)) + for _, s := range str { + if s != nil { + out = append(out, fmt.Sprintf("%v", s)) + } + } + return "'" + strings.Join(out, " ") + "'" +} + +func quoteAll(str ...any) string { + out := make([]string, 0, len(str)) + for _, s := range str { + if s != nil { + out = append(out, fmt.Sprintf("'%v'", s)) + } + } + return strings.Join(out, " ") +} + func indent(spaces int, v string) string { pad := strings.Repeat(" ", spaces) return strings.ReplaceAll(v, "\n", "\n"+pad) diff --git a/internal/template/template.go b/internal/template/template.go index dc2c1f8..470705c 100644 --- a/internal/template/template.go +++ b/internal/template/template.go @@ -14,6 +14,8 @@ func ExecuteFileTemplate(ctx context.Context, text string, templateVars any, err delete(funcMap, "expandenv") funcMap["env"] = env + funcMap["quote"] = quote + funcMap["quoteAll"] = quoteAll funcMap["envDefault"] = envDefault // deprecated diff --git a/internal/util/highlight.go b/internal/util/highlight.go index b91fed5..1ecc76b 100644 --- a/internal/util/highlight.go +++ b/internal/util/highlight.go @@ -2,6 +2,7 @@ package util import ( "bytes" + "fmt" "github.com/alecthomas/chroma" "github.com/alecthomas/chroma/formatters" @@ -11,7 +12,10 @@ import ( ) func Highlight(source string) string { - var out bytes.Buffer + out := &numberWriter{ + w: bytes.NewBufferString(""), + currentLine: 1, + } // Determine lexer. l := lexers.Get("yaml") if l == nil { @@ -38,10 +42,57 @@ func Highlight(source string) string { if err != nil { pterm.Error.Println(err.Error()) } - err = f.Format(&out, s, it) - if err != nil { + + if err = f.Format(out, s, it); err != nil { pterm.Error.Println(err.Error()) } - return out.String() + return out.w.String() +} + +type numberWriter struct { + w *bytes.Buffer + currentLine uint64 + buf []byte +} + +func (w *numberWriter) Write(p []byte) (int, error) { + // Early return. + // Can't calculate the line numbers until the line breaks are made, so store them all in a buffer. + if !bytes.Contains(p, []byte{'\n'}) { + w.buf = append(w.buf, p...) + return len(p), nil + } + + var ( + original = p + tokenLen uint + ) + for i, c := range original { + tokenLen++ + if c != '\n' { + continue + } + + token := p[:tokenLen] + p = original[i+1:] + tokenLen = 0 + + format := "%4d |\t%s%s" + if w.currentLine > 9999 { + format = "%d |\t%s%s" + } + format = "\033[34m" + format + "\033[0m" + + if _, err := fmt.Fprintf(w.w, format, w.currentLine, string(w.buf), string(token)); err != nil { + return i + 1, err + } + w.buf = w.buf[:0] + w.currentLine++ + } + + if len(p) > 0 { + w.buf = append(w.buf, p...) + } + return len(original), nil } diff --git a/squadron.go b/squadron.go index 4fbcdac..9d90d47 100644 --- a/squadron.go +++ b/squadron.go @@ -11,6 +11,7 @@ import ( "slices" "strings" "sync" + "time" "github.com/foomo/squadron/internal/config" templatex "github.com/foomo/squadron/internal/template" @@ -68,7 +69,7 @@ func (sq *Squadron) ConfigYAML() string { // ~ Public methods // ------------------------------------------------------------------------------------------------ -func (sq *Squadron) MergeConfigFiles() error { +func (sq *Squadron) MergeConfigFiles(ctx context.Context) error { pterm.Debug.Println("merging config files") pterm.Debug.Println(strings.Join(append([]string{"using files"}, sq.files...), "\nā”” ")) @@ -87,7 +88,7 @@ func (sq *Squadron) MergeConfigFiles() error { return errors.New("Please upgrade your YAML definition to 2.0") } - sq.c.Trim() + sq.c.Trim(ctx) value, err := yamlv2.Marshal(sq.c) if err != nil { @@ -99,7 +100,7 @@ func (sq *Squadron) MergeConfigFiles() error { return nil } -func (sq *Squadron) FilterConfig(squadron string, units, tags []string) error { +func (sq *Squadron) FilterConfig(ctx context.Context, squadron string, units, tags []string) error { if len(squadron) > 0 { if err := sq.Config().Squadrons.Filter(squadron); err != nil { return err @@ -113,7 +114,7 @@ func (sq *Squadron) FilterConfig(squadron string, units, tags []string) error { } if len(tags) > 0 { - if err := sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { + if err := sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { return value.FilterFn(func(k string, v *config.Unit) bool { for _, tag := range tags { if strings.HasPrefix(tag, "-") { @@ -131,7 +132,7 @@ func (sq *Squadron) FilterConfig(squadron string, units, tags []string) error { } } - sq.c.Trim() + sq.c.Trim(ctx) value, err := yamlv2.Marshal(sq.c) if err != nil { @@ -169,22 +170,22 @@ func (sq *Squadron) RenderConfig(ctx context.Context) error { // execute without errors to get existing values pterm.Debug.Println("executing file template") // pterm.Debug.Println(sq.config) - out, err := templatex.ExecuteFileTemplate(ctx, sq.config, tv, false) + out1, err := templatex.ExecuteFileTemplate(ctx, sq.config, tv, false) if err != nil { return errors.Wrapf(err, "failed to execute initial file template\n%s", util.Highlight(sq.config)) } // re-execute for rendering copied values pterm.Debug.Println("re-executing file template") - // pterm.Debug.Println(string(out)) - out, err = templatex.ExecuteFileTemplate(ctx, string(out), tv, false) + out2, err := templatex.ExecuteFileTemplate(ctx, string(out1), tv, false) if err != nil { + fmt.Print(util.Highlight(string(out1))) return errors.Wrap(err, "failed to re-execute initial file template") } pterm.Debug.Println("unmarshalling vars") - if err := yaml.Unmarshal(out, &vars); err != nil { - pterm.Error.Println(string(out)) + if err := yaml.Unmarshal(out2, &vars); err != nil { + fmt.Print(util.Highlight(string(out2))) return errors.Wrap(err, "failed to unmarshal vars") } @@ -204,34 +205,47 @@ func (sq *Squadron) RenderConfig(ctx context.Context) error { } pterm.Debug.Println("executing file template") - out, err = templatex.ExecuteFileTemplate(ctx, sq.config, tv, true) + out3, err := templatex.ExecuteFileTemplate(ctx, sq.config, tv, true) if err != nil { + fmt.Print(util.Highlight(sq.config)) return errors.Wrap(err, "failed to execute second file template") } pterm.Debug.Println("unmarshalling vars") - if err := yaml.Unmarshal(out, &sq.c); err != nil { - pterm.Error.Println(string(out)) + if err := yaml.Unmarshal(out3, &sq.c); err != nil { + fmt.Print(util.Highlight(string(out3))) return errors.Wrap(err, "failed to unmarshal vars") } - sq.config = string(out) + sq.config = string(out3) return nil } func (sq *Squadron) Push(ctx context.Context, pushArgs []string, parallel int) error { - wg, gctx := errgroup.WithContext(ctx) + wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) - _ = sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { - return value.Iterate(func(k string, v *config.Unit) error { - wg.Go(func() error { - if out, err := v.Push(gctx, key, k, pushArgs); err != nil { - return errors.Wrap(err, out) - } - return nil - }) + _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { + for _, name := range v.BuildNames() { + build := v.Builds[name] + id := fmt.Sprintf("%s/%s.%s", key, k, name) + wg.Go(func() error { + if err := ctx.Err(); err != nil { + return err + } + + start := time.Now() + pterm.Info.Printfln("Push | %s [%s:%s]", id, build.Image, build.Tag) + if out, err := build.Push(ctx, pushArgs); err != nil { + pterm.Error.Printfln("Push | %s [%s:%s] ā± %s", id, build.Image, build.Tag, time.Since(start).Round(time.Millisecond)) + return errors.Wrap(err, out) + } + pterm.Success.Printfln("Push | %s [%s:%s] ā± %s", id, build.Image, build.Tag, time.Since(start).Round(time.Millisecond)) + return nil + }) + } return nil }) }) @@ -240,21 +254,23 @@ func (sq *Squadron) Push(ctx context.Context, pushArgs []string, parallel int) e } func (sq *Squadron) BuildDependencies(ctx context.Context, buildArgs []string, parallel int) error { - wg, gctx := errgroup.WithContext(ctx) + wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) - var i int - dependencies := sq.c.BuildDependencies() - for name, dependency := range dependencies { - i := i + 1 + dependencies := sq.c.BuildDependencies(ctx) + for name, build := range dependencies { wg.Go(func() error { - pterm.Info.Printfln("[%d/%d] Building dependency `%s`", i, len(dependencies), name) - pterm.FgGray.Printfln("ā”” %s:%s", dependency.Image, dependency.Tag) - if out, err := dependency.Build(gctx, buildArgs); err != nil { - pterm.Error.Printfln("[%d/%d] Failed to build dependency `%s`", i, len(dependencies), name) - pterm.FgGray.Printfln("ā”” %s:%s", dependency.Image, dependency.Tag) + if err := ctx.Err(); err != nil { + return err + } + + start := time.Now() + pterm.Info.Printfln("Build | %s [%s:%s]", name, build.Image, build.Tag) + if out, err := build.Build(ctx, buildArgs); err != nil { + pterm.Error.Printfln("Build | %s [%s:%s] ā± %s", name, build.Image, build.Tag, time.Since(start).Round(time.Millisecond)) return errors.Wrap(err, out) } + pterm.Success.Printfln("Build | %s [%s:%s] ā± %s", name, build.Image, build.Tag, time.Since(start).Round(time.Millisecond)) return nil }) } @@ -267,17 +283,29 @@ func (sq *Squadron) Build(ctx context.Context, buildArgs []string, parallel int) return err } - wg, gctx := errgroup.WithContext(ctx) + wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) - _ = sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { - return value.Iterate(func(k string, v *config.Unit) error { - wg.Go(func() error { - if out, err := v.Build(gctx, key, k, buildArgs); err != nil { - return errors.Wrap(err, out) - } - return nil - }) + _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { + for _, name := range v.BuildNames() { + build := v.Builds[name] + id := fmt.Sprintf("%s/%s.%s", key, k, name) + wg.Go(func() error { + if err := ctx.Err(); err != nil { + return err + } + + start := time.Now() + pterm.Info.Printfln("Build | %s [%s:%s]", id, build.Image, build.Tag) + if out, err := build.Build(ctx, buildArgs); err != nil { + pterm.Error.Printfln("Build | %s [%s:%s] ā± %s", id, build.Image, build.Tag, time.Since(start).Round(time.Millisecond)) + return errors.Wrap(err, out) + } + pterm.Success.Printfln("Build | %s [%s:%s] ā± %s", id, build.Image, build.Tag, time.Since(start).Round(time.Millisecond)) + return nil + }) + } return nil }) }) @@ -289,9 +317,14 @@ func (sq *Squadron) Down(ctx context.Context, helmArgs []string, parallel int) e wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) - _ = sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { - return value.Iterate(func(k string, v *config.Unit) error { + _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { wg.Go(func() error { + if err := ctx.Err(); err != nil { + return err + } + + start := time.Now() name := fmt.Sprintf("%s-%s", key, k) namespace, err := sq.Namespace(ctx, key, k) if err != nil { @@ -299,7 +332,7 @@ func (sq *Squadron) Down(ctx context.Context, helmArgs []string, parallel int) e } stdErr := bytes.NewBuffer([]byte{}) - pterm.Debug.Printfln("running helm uninstall for: %s", name) + pterm.Info.Printfln("Down | %s/%s", key, k) if out, err := util.NewHelmCommand().Args("uninstall", name). Stderr(stdErr). Stdout(os.Stdout). @@ -307,8 +340,10 @@ func (sq *Squadron) Down(ctx context.Context, helmArgs []string, parallel int) e Args(helmArgs...). Run(ctx); err != nil && string(bytes.TrimSpace(stdErr.Bytes())) != fmt.Sprintf("Error: uninstall: Release not loaded: %s: release: not found", name) { + pterm.Error.Printfln("Down | %s/%s] ā± %s", key, k, time.Since(start).Round(time.Millisecond)) return errors.Wrap(err, out) } + pterm.Success.Printfln("Down | %s/%s] ā± %s", key, k, time.Since(start).Round(time.Millisecond)) return nil }) return nil @@ -330,9 +365,13 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) e wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) - _ = sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { - return value.Iterate(func(k string, v *config.Unit) error { + _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { wg.Go(func() error { + if err := ctx.Err(); err != nil { + return err + } + name := fmt.Sprintf("%s-%s", key, k) namespace, err := sq.Namespace(ctx, key, k) if err != nil { @@ -422,8 +461,8 @@ func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int) wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) - _ = sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { - return value.Iterate(func(k string, v *config.Unit) error { + _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { var status statusType name := fmt.Sprintf("%s-%s", key, k) namespace, err := sq.Namespace(ctx, key, k) @@ -486,8 +525,8 @@ func (sq *Squadron) Rollback(ctx context.Context, revision string, helmArgs []st wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) - _ = sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { - return value.Iterate(func(k string, v *config.Unit) error { + _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { name := fmt.Sprintf("%s-%s", key, k) namespace, err := sq.Namespace(ctx, key, k) if err != nil { @@ -518,8 +557,8 @@ func (sq *Squadron) Rollback(ctx context.Context, revision string, helmArgs []st func (sq *Squadron) UpdateLocalDependencies(ctx context.Context, parallel int) error { // collect unique entrie repositories := map[string]struct{}{} - err := sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { - return value.Iterate(func(k string, v *config.Unit) error { + err := sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { if strings.HasPrefix(v.Chart.Repository, "file:///") { repositories[v.Chart.Repository] = struct{}{} } @@ -530,7 +569,7 @@ func (sq *Squadron) UpdateLocalDependencies(ctx context.Context, parallel int) e return err } - wg, gctx := errgroup.WithContext(ctx) + wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) for repository := range repositories { @@ -539,7 +578,7 @@ func (sq *Squadron) UpdateLocalDependencies(ctx context.Context, parallel int) e if out, err := util.NewHelmCommand(). Cwd(path.Clean(strings.TrimPrefix(repository, "file://"))). Args("dependency", "update", "--skip-refresh", "--debug"). - Run(gctx); err != nil { + Run(ctx); err != nil { return errors.Wrap(err, out) } else { pterm.Debug.Println(out) @@ -554,12 +593,16 @@ func (sq *Squadron) UpdateLocalDependencies(ctx context.Context, parallel int) e func (sq *Squadron) Up(ctx context.Context, helmArgs []string, username, version, commit, branch string, parallel int) error { description := fmt.Sprintf("\nDeployed-By: %s\nManaged-By: Squadron %s\nGit-Commit: %s\nGit-Branch: %s", username, version, commit, branch) - wg, gctx := errgroup.WithContext(ctx) + wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) - _ = sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { - return value.Iterate(func(k string, v *config.Unit) error { + _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { wg.Go(func() error { + if err := ctx.Err(); err != nil { + return err + } + name := fmt.Sprintf("%s-%s", key, k) namespace, err := sq.Namespace(ctx, key, k) if err != nil { @@ -597,7 +640,7 @@ func (sq *Squadron) Up(ctx context.Context, helmArgs []string, username, version } } - if out, err := cmd.Run(gctx); err != nil { + if out, err := cmd.Run(ctx); err != nil { return errors.Wrap(err, out) } @@ -620,12 +663,16 @@ func (sq *Squadron) Template(ctx context.Context, helmArgs []string, parallel in return err } - wg, gctx := errgroup.WithContext(ctx) + wg, ctx := errgroup.WithContext(ctx) wg.SetLimit(parallel) - _ = sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error { - return value.Iterate(func(k string, v *config.Unit) error { + _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { + return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { wg.Go(func() error { + if err := ctx.Err(); err != nil { + return err + } + name := fmt.Sprintf("%s-%s", key, k) namespace, err := sq.Namespace(ctx, key, k) if err != nil { @@ -633,7 +680,7 @@ func (sq *Squadron) Template(ctx context.Context, helmArgs []string, parallel in } pterm.Debug.Printfln("running helm template for chart: %s", name) - out, err := v.Template(gctx, name, key, k, namespace, sq.c.Global, sq.c.Vars, helmArgs) + out, err := v.Template(ctx, name, key, k, namespace, sq.c.Global, sq.c.Vars, helmArgs) if err != nil { return err } diff --git a/squadron_test.go b/squadron_test.go index c0ed3b2..9546bcb 100644 --- a/squadron_test.go +++ b/squadron_test.go @@ -73,11 +73,11 @@ func config(t *testing.T, name string, files []string, squadronName string, unit sq := squadron.New(cwd, "default", files) { - require.NoError(t, sq.MergeConfigFiles(), "failed to merge files") + require.NoError(t, sq.MergeConfigFiles(ctx), "failed to merge files") } { - require.NoError(t, sq.FilterConfig(squadronName, unitNames, tags), "failed to filter config") + require.NoError(t, sq.FilterConfig(ctx, squadronName, unitNames, tags), "failed to filter config") testutils.Snapshot(t, path.Join("testdata", name, "snapshop-config-norender.yaml"), sq.ConfigYAML()) }