mirror of
https://github.com/foomo/squadron.git
synced 2025-10-16 12:35:42 +00:00
feat: parallel and better output
This commit is contained in:
parent
816d455c24
commit
fa7ab36551
@ -4,47 +4,60 @@ import (
|
||||
"github.com/foomo/squadron"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
buildCmd.Flags().BoolVarP(&flagPush, "push", "p", false, "pushes built squadron units to the registry")
|
||||
buildCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
|
||||
buildCmd.Flags().StringArrayVar(&flagBuildArgs, "build-args", nil, "additional docker buildx build args")
|
||||
buildCmd.Flags().StringArrayVar(&flagPushArgs, "push-args", nil, "additional docker push args")
|
||||
buildCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
func NewBuild(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "build [SQUADRON.UNIT...]",
|
||||
Short: "build or rebuild squadron units",
|
||||
Example: "squadron build storefinder frontend backend",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, "", c.GetStringSlice("file"))
|
||||
|
||||
var buildCmd = &cobra.Command{
|
||||
Use: "build [SQUADRON.UNIT...]",
|
||||
Short: "build or rebuild squadron units",
|
||||
Example: " squadron build storefinder frontend backend",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, "", flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
if err := sq.Build(cmd.Context(), flagBuildArgs, flagParallel); err != nil {
|
||||
return errors.Wrap(err, "failed to build units")
|
||||
}
|
||||
|
||||
if flagPush {
|
||||
if err := sq.Push(cmd.Context(), flagPushArgs, flagParallel); err != nil {
|
||||
return errors.Wrap(err, "failed to push units")
|
||||
if err := sq.MergeConfigFiles(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to merge config files")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
squadronName, unitNames := parseSquadronAndUnitNames(args)
|
||||
if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
if err := sq.Build(cmd.Context(), c.GetStringSlice("build-args"), c.GetInt("parallel")); err != nil {
|
||||
return errors.Wrap(err, "failed to build units")
|
||||
}
|
||||
|
||||
if c.GetBool("push") {
|
||||
if err := sq.Push(cmd.Context(), c.GetStringSlice("push-args"), c.GetInt("parallel")); err != nil {
|
||||
return errors.Wrap(err, "failed to push units")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolP("push", "p", false, "pushes built squadron units to the registry")
|
||||
_ = c.BindPFlag("push", flags.Lookup("push"))
|
||||
|
||||
cmd.Flags().Int("parallel", 1, "run command in parallel")
|
||||
_ = c.BindPFlag("parallel", flags.Lookup("parallel"))
|
||||
|
||||
flags.StringArray("build-args", nil, "additional docker buildx build args")
|
||||
_ = c.BindPFlag("build-args", flags.Lookup("build-args"))
|
||||
|
||||
flags.StringArray("push-args", nil, "additional docker push args")
|
||||
_ = c.BindPFlag("push-args", flags.Lookup("push-args"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -4,12 +4,14 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish|powershell]",
|
||||
Short: "Generate completion script",
|
||||
Long: `To load completions:
|
||||
func NewCompletion(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish|powershell]",
|
||||
Short: "Generate completion script",
|
||||
Long: `To load completions:
|
||||
|
||||
Bash:
|
||||
|
||||
@ -48,20 +50,23 @@ PowerShell:
|
||||
PS> squadron completion powershell > squadron.ps1
|
||||
# and source this file from your PowerShell profile.
|
||||
`,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
switch args[0] {
|
||||
case "bash":
|
||||
return cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
return cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
return cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
switch args[0] {
|
||||
case "bash":
|
||||
return cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
return cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
return cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -1,44 +1,50 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/foomo/squadron"
|
||||
"github.com/foomo/squadron/internal/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
configCmd.Flags().BoolVar(&flagNoRender, "no-render", false, "don't render the config template")
|
||||
configCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
func NewConfig(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config [SQUADRON] [UNIT...]",
|
||||
Short: "generate and view the squadron config",
|
||||
Example: " squadron config storefinder frontend backend",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, "", c.GetStringSlice("file"))
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config [SQUADRON] [UNIT...]",
|
||||
Short: "generate and view the squadron config",
|
||||
Example: " squadron config storefinder frontend backend",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, "", flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if !flagNoRender {
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
if err := sq.MergeConfigFiles(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to merge config files")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print(util.Highlight(sq.ConfigYAML()))
|
||||
squadronName, unitNames := parseSquadronAndUnitNames(args)
|
||||
if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
if !c.GetBool("no-render") {
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
}
|
||||
|
||||
pterm.Println(util.Highlight(sq.ConfigYAML()))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Bool("no-render", false, "don't render the config template")
|
||||
_ = c.BindPFlag("no-render", flags.Lookup("no-render"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -2,42 +2,60 @@ package actions
|
||||
|
||||
import (
|
||||
"github.com/foomo/squadron"
|
||||
"github.com/foomo/squadron/internal/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
diffCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
diffCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
|
||||
diffCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
|
||||
var diffCmd = &cobra.Command{
|
||||
Use: "diff [SQUADRON] [UNIT...]",
|
||||
Short: "shows the diff between the installed and local chart",
|
||||
Example: " squadron diff storefinder frontend backend --namespace demo",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, flagNamespace, flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
if err := sq.UpdateLocalDependencies(cmd.Context(), flagParallel); err != nil {
|
||||
return errors.Wrap(err, "failed to update dependencies")
|
||||
}
|
||||
|
||||
return sq.Diff(cmd.Context(), helmArgs, flagParallel)
|
||||
},
|
||||
func NewDiff(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "diff [SQUADRON] [UNIT...]",
|
||||
Short: "shows the diff between the installed and local chart",
|
||||
Example: " squadron diff storefinder frontend backend --namespace demo",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, c.GetString("namespace"), c.GetStringSlice("file"))
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
if err := sq.UpdateLocalDependencies(cmd.Context(), c.GetInt("parallel")); err != nil {
|
||||
return errors.Wrap(err, "failed to update dependencies")
|
||||
}
|
||||
|
||||
out, err := sq.Diff(cmd.Context(), helmArgs, c.GetInt("parallel"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pterm.Println(util.Highlight(out))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.StringP("namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
_ = c.BindPFlag("namespace", flags.Lookup("namespace"))
|
||||
|
||||
flags.Int("parallel", 1, "run command in parallel")
|
||||
_ = c.BindPFlag("parallel", flags.Lookup("parallel"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -3,35 +3,44 @@ package actions
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/foomo/squadron"
|
||||
)
|
||||
|
||||
func init() {
|
||||
downCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
|
||||
downCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
downCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
|
||||
var downCmd = &cobra.Command{
|
||||
Use: "down [SQUADRON] [UNIT...]",
|
||||
Short: "uninstalls the squadron or given units",
|
||||
Example: " squadron down storefinder frontend backend --namespace demo",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, flagNamespace, flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
return sq.Down(cmd.Context(), helmArgs, flagParallel)
|
||||
},
|
||||
func NewDown(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "down [SQUADRON] [UNIT...]",
|
||||
Short: "uninstalls the squadron or given units",
|
||||
Example: " squadron down storefinder frontend backend --namespace demo",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, c.GetString("namespace"), c.GetStringSlice("file"))
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
return sq.Down(cmd.Context(), helmArgs, c.GetInt("parallel"))
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Int("parallel", 1, "run command in parallel")
|
||||
_ = c.BindPFlag("parallel", flags.Lookup("parallel"))
|
||||
|
||||
flags.StringP("namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
_ = c.BindPFlag("namespace", flags.Lookup("namespace"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -9,69 +9,75 @@ import (
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/pterm/pterm/putils"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
flagWithBuilds bool
|
||||
flagWithCharts bool
|
||||
flagWithTags bool
|
||||
)
|
||||
func NewList(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list [SQUADRON]",
|
||||
Short: "list squadron units",
|
||||
Example: " squadron list storefinder",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, "", c.GetStringSlice("file"))
|
||||
|
||||
func init() {
|
||||
listCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
listCmd.Flags().BoolVar(&flagWithTags, "with-tags", false, "include tags")
|
||||
listCmd.Flags().BoolVar(&flagWithCharts, "with-charts", false, "include charts")
|
||||
listCmd.Flags().BoolVar(&flagWithBuilds, "with-builds", false, "include builds")
|
||||
}
|
||||
if err := sq.MergeConfigFiles(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to merge config files")
|
||||
}
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list [SQUADRON]",
|
||||
Short: "list squadron units",
|
||||
Example: " squadron list storefinder",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, "", flagFiles)
|
||||
squadronName, unitNames := parseSquadronAndUnitNames(args)
|
||||
if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.MergeConfigFiles(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to merge config files")
|
||||
}
|
||||
var list pterm.LeveledList
|
||||
|
||||
squadronName, unitNames := parseSquadronAndUnitNames(args)
|
||||
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(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(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()})
|
||||
}
|
||||
if flagWithCharts && len(v.Chart.String()) > 0 {
|
||||
list = append(list, pterm.LeveledListItem{Level: 2, Text: "📑: " + v.Chart.String()})
|
||||
}
|
||||
if flagWithBuilds && len(v.Builds) > 0 {
|
||||
for name, build := range v.Builds {
|
||||
list = append(list, pterm.LeveledListItem{Level: 2, Text: "📦: " + name})
|
||||
for _, dependency := range build.Dependencies {
|
||||
list = append(list, pterm.LeveledListItem{Level: 3, Text: "🗃️: " + dependency})
|
||||
// List squadrons
|
||||
_ = 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(ctx, func(ctx context.Context, k string, v *config.Unit) error {
|
||||
list = append(list, pterm.LeveledListItem{Level: 1, Text: k})
|
||||
if c.GetBool("with-tags") && len(v.Tags) > 0 {
|
||||
list = append(list, pterm.LeveledListItem{Level: 2, Text: "🔖: " + v.Tags.SortedString()})
|
||||
}
|
||||
if c.GetBool("with-charts") && len(v.Chart.String()) > 0 {
|
||||
list = append(list, pterm.LeveledListItem{Level: 2, Text: "📑: " + v.Chart.String()})
|
||||
}
|
||||
if c.GetBool("with-builds") && len(v.Builds) > 0 {
|
||||
for name, build := range v.Builds {
|
||||
list = append(list, pterm.LeveledListItem{Level: 2, Text: "📦: " + name})
|
||||
for _, dependency := range build.Dependencies {
|
||||
list = append(list, pterm.LeveledListItem{Level: 3, Text: "🗃️: " + dependency})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if len(list) > 0 {
|
||||
root := putils.TreeFromLeveledList(list)
|
||||
root.Text = "Squadron"
|
||||
return pterm.DefaultTree.WithRoot(root).Render()
|
||||
}
|
||||
if len(list) > 0 {
|
||||
root := putils.TreeFromLeveledList(list)
|
||||
root.Text = "Squadron"
|
||||
return pterm.DefaultTree.WithRoot(root).Render()
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
flags.Bool("with-tags", false, "include tags")
|
||||
_ = c.BindPFlag("with-tags", flags.Lookup("with-tags"))
|
||||
|
||||
flags.Bool("with-charts", false, "include charts")
|
||||
_ = c.BindPFlag("with-charts", flags.Lookup("with-charts"))
|
||||
|
||||
flags.Bool("with-builds", false, "include builds")
|
||||
_ = c.BindPFlag("with-builds", flags.Lookup("with-builds"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -7,31 +7,34 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
var postRendererCmd = &cobra.Command{
|
||||
Use: "post-renderer [PATH]",
|
||||
Hidden: true,
|
||||
Short: "render chart templates locally and display the output",
|
||||
Example: " squadron template storefinder frontend backend --namespace demo",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// this does the trick
|
||||
r, err := io.ReadAll(cmd.InOrStdin())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(path.Join(args[0], ".chart.yaml"), r, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c := exec.CommandContext(cmd.Context(), "kustomize", "build", args[0])
|
||||
c.Stdout = os.Stdout
|
||||
return c.Run()
|
||||
},
|
||||
func NewPostRenderer(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "post-renderer [PATH]",
|
||||
Hidden: true,
|
||||
Short: "render chart templates locally and display the output",
|
||||
Example: " squadron template storefinder frontend backend --namespace demo",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// this does the trick
|
||||
r, err := io.ReadAll(cmd.InOrStdin())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(path.Join(args[0], ".chart.yaml"), r, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c := exec.CommandContext(cmd.Context(), "kustomize", "build", args[0])
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
return c.Run()
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -4,43 +4,58 @@ import (
|
||||
"github.com/foomo/squadron"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
pushCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
pushCmd.Flags().BoolVarP(&flagBuild, "build", "b", false, "builds or rebuilds units")
|
||||
pushCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
|
||||
pushCmd.Flags().StringArrayVar(&flagBuildArgs, "build-args", nil, "additional docker buildx build args")
|
||||
pushCmd.Flags().StringArrayVar(&flagPushArgs, "push-args", nil, "additional docker push args")
|
||||
pushCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
func NewPush(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "push [SQUADRON] [UNIT...]",
|
||||
Short: "pushes the squadron or given units",
|
||||
Example: " squadron push storefinder frontend backend --namespace demo --build",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, c.GetString("namespace"), c.GetStringSlice("file"))
|
||||
|
||||
var pushCmd = &cobra.Command{
|
||||
Use: "push [SQUADRON] [UNIT...]",
|
||||
Short: "pushes the squadron or given units",
|
||||
Example: " squadron push storefinder frontend backend --namespace demo --build",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, flagNamespace, flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
if flagBuild {
|
||||
if err := sq.Build(cmd.Context(), flagBuildArgs, flagParallel); err != nil {
|
||||
return errors.Wrap(err, "failed to build units")
|
||||
if err := sq.MergeConfigFiles(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to merge config files")
|
||||
}
|
||||
}
|
||||
|
||||
return sq.Push(cmd.Context(), flagPushArgs, flagParallel)
|
||||
},
|
||||
squadronName, unitNames := parseSquadronAndUnitNames(args)
|
||||
if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
if c.GetBool("build") {
|
||||
if err := sq.Build(cmd.Context(), c.GetStringSlice("build-args"), c.GetInt("parallel")); err != nil {
|
||||
return errors.Wrap(err, "failed to build units")
|
||||
}
|
||||
}
|
||||
|
||||
return sq.Push(cmd.Context(), c.GetStringSlice("push-args"), c.GetInt("parallel"))
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.StringP("namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
_ = c.BindPFlag("namespace", flags.Lookup("namespace"))
|
||||
|
||||
flags.BoolP("build", "b", false, "builds or rebuilds units")
|
||||
_ = c.BindPFlag("build", flags.Lookup("build"))
|
||||
|
||||
flags.Int("parallel", 1, "run command in parallel")
|
||||
_ = c.BindPFlag("parallel", flags.Lookup("parallel"))
|
||||
|
||||
flags.StringArray("build-args", nil, "additional docker buildx build args")
|
||||
_ = c.BindPFlag("build-args", flags.Lookup("build-args"))
|
||||
|
||||
flags.StringArray("push-args", nil, "additional docker push args")
|
||||
_ = c.BindPFlag("push-args", flags.Lookup("push-args"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -3,35 +3,46 @@ package actions
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/foomo/squadron"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rollbackCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
|
||||
rollbackCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
rollbackCmd.Flags().StringVarP(&flagRevision, "revision", "r", "", "specifies the revision to roll back to")
|
||||
rollbackCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
|
||||
var rollbackCmd = &cobra.Command{
|
||||
Use: "rollback [SQUADRON] [UNIT...]",
|
||||
Short: "rolls back the squadron or given units",
|
||||
Example: " squadron rollback storefinder frontend backend --namespace demo",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, flagNamespace, flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
return sq.Rollback(cmd.Context(), flagRevision, helmArgs, flagParallel)
|
||||
},
|
||||
func NewRollback(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "rollback [SQUADRON] [UNIT...]",
|
||||
Short: "rolls back the squadron or given units",
|
||||
Example: " squadron rollback storefinder frontend backend --namespace demo",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, c.GetString("namespace"), c.GetStringSlice("file"))
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
return sq.Rollback(cmd.Context(), c.GetString("revision"), helmArgs, c.GetInt("parallel"))
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Int("parallel", 1, "run command in parallel")
|
||||
_ = c.BindPFlag("parallel", flags.Lookup("parallel"))
|
||||
|
||||
flags.StringP("namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
_ = c.BindPFlag("namespace", flags.Lookup("namespace"))
|
||||
|
||||
flags.StringP("revision", "r", "", "specifies the revision to roll back to")
|
||||
_ = c.BindPFlag("revision", flags.Lookup("revision"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("namespace"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -1,75 +1,103 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
cowsay "github.com/Code-Hex/Neo-cowsay/v2"
|
||||
"github.com/foomo/squadron/internal/cmd"
|
||||
"github.com/foomo/squadron/internal/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
cwd string
|
||||
root *cobra.Command
|
||||
)
|
||||
|
||||
func init() {
|
||||
root = NewRoot()
|
||||
root.AddCommand(
|
||||
NewUp(NewViper(root)),
|
||||
NewDiff(NewViper(root)),
|
||||
NewDown(NewViper(root)),
|
||||
NewBuild(NewViper(root)),
|
||||
NewPush(NewViper(root)),
|
||||
NewList(NewViper(root)),
|
||||
NewRollback(NewViper(root)),
|
||||
NewStatus(NewViper(root)),
|
||||
NewConfig(NewViper(root)),
|
||||
NewVersion(NewViper(root)),
|
||||
NewCompletion(NewViper(root)),
|
||||
NewTemplate(NewViper(root)),
|
||||
NewPostRenderer(NewViper(root)),
|
||||
NewSchema(NewViper(root)),
|
||||
)
|
||||
}
|
||||
|
||||
// NewRoot represents the base command when called without any subcommands
|
||||
func NewRoot() *cobra.Command {
|
||||
root := &cobra.Command{
|
||||
Use: "squadron",
|
||||
Short: "Docker compose for kubernetes",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if flagSilent {
|
||||
logrus.SetLevel(logrus.ErrorLevel)
|
||||
} else if flagDebug {
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
if viper.GetBool("debug") {
|
||||
pterm.EnableDebugMessages()
|
||||
} else if flagVerbose {
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
} else {
|
||||
logrus.SetLevel(logrus.WarnLevel)
|
||||
}
|
||||
if cmd.Name() == "help" || cmd.Name() == "init" || cmd.Name() == "version" {
|
||||
return nil
|
||||
}
|
||||
// cwd
|
||||
return util.ValidatePath(".", &cwd)
|
||||
},
|
||||
}
|
||||
|
||||
cwd string
|
||||
flagSilent bool
|
||||
flagDebug bool
|
||||
flagVerbose bool
|
||||
flagNoRender bool
|
||||
flagNamespace string
|
||||
flagRevision string
|
||||
flagBuild bool
|
||||
flagPush bool
|
||||
flagParallel int
|
||||
flagBuildArgs []string
|
||||
flagPushArgs []string
|
||||
flagTags []string
|
||||
flagFiles []string
|
||||
)
|
||||
flags := root.PersistentFlags()
|
||||
flags.BoolP("debug", "d", false, "show all output")
|
||||
_ = viper.BindPFlag("debug", root.PersistentFlags().Lookup("debug"))
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().BoolVarP(&flagSilent, "silent", "s", false, "only show errors")
|
||||
rootCmd.PersistentFlags().BoolVarP(&flagDebug, "debug", "d", false, "show all output")
|
||||
rootCmd.PersistentFlags().BoolVarP(&flagVerbose, "verbose", "v", false, "show more output")
|
||||
rootCmd.PersistentFlags().StringSliceVarP(&flagFiles, "file", "f", []string{"squadron.yaml"}, "specify alternative squadron files")
|
||||
flags.StringSliceP("file", "f", []string{"squadron.yaml"}, "specify alternative squadron files")
|
||||
|
||||
rootCmd.AddCommand(upCmd, diffCmd, downCmd, buildCmd, pushCmd, listCmd, rollbackCmd, statusCmd, configCmd, versionCmd, completionCmd, templateCmd, postRendererCmd, schemaCmd)
|
||||
return root
|
||||
}
|
||||
|
||||
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 NewViper(root *cobra.Command) *viper.Viper {
|
||||
c := viper.New()
|
||||
_ = c.BindPFlag("file", root.PersistentFlags().Lookup("file"))
|
||||
return c
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
pterm.Error.Println(err.Error())
|
||||
os.Exit(1)
|
||||
l := cmd.NewLogger()
|
||||
|
||||
say := func(msg string) string {
|
||||
if say, cerr := cowsay.Say(msg, cowsay.BallonWidth(80)); cerr == nil {
|
||||
msg = say
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
code := 0
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
l.Error(say("It's time to panic"))
|
||||
l.Error(fmt.Sprintf("%v", r))
|
||||
l.Error(string(debug.Stack()))
|
||||
code = 1
|
||||
}
|
||||
os.Exit(code)
|
||||
}()
|
||||
|
||||
if err := root.Execute(); err != nil {
|
||||
l.Error(say(strings.Split(errors.Cause(err).Error(), ":")[0]))
|
||||
l.Error(util.SprintError(err))
|
||||
code = 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/foomo/squadron"
|
||||
@ -9,50 +8,54 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
flagOutput string
|
||||
flagBaseSchema string
|
||||
)
|
||||
func NewSchema(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "schema [SQUADRON]",
|
||||
Short: "generate squadron json schema",
|
||||
Example: " squadron schema",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, "", c.GetStringSlice("file"))
|
||||
|
||||
func init() {
|
||||
schemaCmd.Flags().StringVar(&flagOutput, "output", "", "Output file")
|
||||
schemaCmd.Flags().StringVar(&flagBaseSchema, "base-schema", "https://raw.githubusercontent.com/foomo/squadron/refs/heads/main/squadron.schema.json", "Base schema to use")
|
||||
schemaCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
|
||||
var schemaCmd = &cobra.Command{
|
||||
Use: "schema [SQUADRON]",
|
||||
Short: "generate squadron json schema",
|
||||
Example: " squadron schema",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, "", flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
js, err := sq.RenderSchema(cmd.Context(), flagBaseSchema)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to render schema")
|
||||
}
|
||||
|
||||
if flagOutput != "" {
|
||||
pterm.Info.Printfln("Writing JSON schema to %s", flagOutput)
|
||||
if err := os.WriteFile(flagOutput, []byte(js), 0600); err != nil {
|
||||
return errors.Wrap(err, "failed to write schema")
|
||||
if err := sq.MergeConfigFiles(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to merge config files")
|
||||
}
|
||||
} else {
|
||||
fmt.Print(util.Highlight(js))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
squadronName, unitNames := parseSquadronAndUnitNames(args)
|
||||
if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
js, err := sq.RenderSchema(cmd.Context(), c.GetString("base-schema"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to render schema")
|
||||
}
|
||||
|
||||
if output := c.GetString("output"); output != "" {
|
||||
pterm.Info.Printfln("Writing JSON schema to %s", output)
|
||||
if err := os.WriteFile(output, []byte(js), 0600); err != nil {
|
||||
return errors.Wrap(err, "failed to write schema")
|
||||
}
|
||||
} else {
|
||||
pterm.Println(util.Highlight(js))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.String("output", "", "Output file")
|
||||
_ = c.BindPFlag("output", flags.Lookup("output"))
|
||||
|
||||
flags.String("base-schema", "https://raw.githubusercontent.com/foomo/squadron/refs/heads/main/squadron.schema.json", "Base schema to use")
|
||||
_ = c.BindPFlag("base-schema", flags.Lookup("base-schema"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -3,34 +3,43 @@ package actions
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/foomo/squadron"
|
||||
)
|
||||
|
||||
func init() {
|
||||
statusCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
|
||||
statusCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
statusCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status [SQUADRON] [UNIT...]",
|
||||
Short: "installs the squadron or given units",
|
||||
Example: " squadron status storefinder frontend backend --namespace demo",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, flagNamespace, flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
return sq.Status(cmd.Context(), helmArgs, flagParallel)
|
||||
},
|
||||
func NewStatus(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "status [SQUADRON] [UNIT...]",
|
||||
Short: "installs the squadron or given units",
|
||||
Example: " squadron status storefinder frontend backend --namespace demo",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, c.GetString("namespace"), c.GetStringSlice("file"))
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
return sq.Status(cmd.Context(), helmArgs, c.GetInt("parallel"))
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Int("parallel", 1, "run command in parallel")
|
||||
_ = c.BindPFlag("parallel", flags.Lookup("parallel"))
|
||||
|
||||
flags.StringP("namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
_ = c.BindPFlag("namespace", flags.Lookup("namespace"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -1,54 +1,62 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/foomo/squadron"
|
||||
"github.com/foomo/squadron/internal/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
templateCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
|
||||
templateCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
templateCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
|
||||
var templateCmd = &cobra.Command{
|
||||
Use: "template [SQUADRON] [UNIT...]",
|
||||
Short: "render chart templates locally and display the output",
|
||||
Example: " squadron template storefinder frontend backend --namespace demo",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, flagNamespace, flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
if err := sq.UpdateLocalDependencies(cmd.Context(), flagParallel); err != nil {
|
||||
return errors.Wrap(err, "failed to update dependencies")
|
||||
}
|
||||
|
||||
out, err := sq.Template(cmd.Context(), helmArgs, flagParallel)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to render template")
|
||||
}
|
||||
|
||||
fmt.Print(util.Highlight(out))
|
||||
|
||||
return nil
|
||||
},
|
||||
func NewTemplate(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "template [SQUADRON] [UNIT...]",
|
||||
Short: "render chart templates locally and display the output",
|
||||
Example: " squadron template storefinder frontend backend --namespace demo",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, c.GetString("namespace"), c.GetStringSlice("file"))
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
if err := sq.UpdateLocalDependencies(cmd.Context(), c.GetInt("parallel")); err != nil {
|
||||
return errors.Wrap(err, "failed to update dependencies")
|
||||
}
|
||||
|
||||
out, err := sq.Template(cmd.Context(), helmArgs, c.GetInt("parallel"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to render template")
|
||||
}
|
||||
|
||||
pterm.Println(util.Highlight(out))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Int("parallel", 1, "run command in parallel")
|
||||
_ = c.BindPFlag("parallel", flags.Lookup("parallel"))
|
||||
|
||||
flags.StringP("namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
_ = c.BindPFlag("namespace", flags.Lookup("namespace"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -8,72 +8,89 @@ import (
|
||||
"github.com/foomo/squadron/internal/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
upCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
upCmd.Flags().BoolVarP(&flagBuild, "build", "b", false, "builds or rebuilds units")
|
||||
upCmd.Flags().BoolVarP(&flagPush, "push", "p", false, "pushes units to the registry")
|
||||
upCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
|
||||
upCmd.Flags().StringArrayVar(&flagBuildArgs, "build-args", nil, "additional docker buildx build args")
|
||||
upCmd.Flags().StringArrayVar(&flagPushArgs, "push-args", nil, "additional docker push args")
|
||||
upCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
}
|
||||
func NewUp(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "up [SQUADRON] [UNIT...]",
|
||||
Short: "installs the squadron or given units",
|
||||
Example: " squadron up storefinder frontend backend --namespace demo --build --push -- --dry-run",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, c.GetString("namespace"), c.GetStringSlice("file"))
|
||||
|
||||
var upCmd = &cobra.Command{
|
||||
Use: "up [SQUADRON] [UNIT...]",
|
||||
Short: "installs the squadron or given units",
|
||||
Example: " squadron up storefinder frontend backend --namespace demo --build --push -- --dry-run",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sq := squadron.New(cwd, flagNamespace, flagFiles)
|
||||
|
||||
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(cmd.Context(), squadronName, unitNames, flagTags); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
if flagBuild {
|
||||
if err := sq.Build(cmd.Context(), flagBuildArgs, flagParallel); err != nil {
|
||||
return errors.Wrap(err, "failed to build units")
|
||||
if err := sq.MergeConfigFiles(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to merge config files")
|
||||
}
|
||||
}
|
||||
|
||||
if flagPush {
|
||||
if err := sq.Push(cmd.Context(), flagPushArgs, flagParallel); err != nil {
|
||||
return errors.Wrap(err, "failed to push units")
|
||||
args, helmArgs := parseExtraArgs(args)
|
||||
|
||||
squadronName, unitNames := parseSquadronAndUnitNames(args)
|
||||
if err := sq.FilterConfig(cmd.Context(), squadronName, unitNames, c.GetStringSlice("tags")); err != nil {
|
||||
return errors.Wrap(err, "failed to filter config")
|
||||
}
|
||||
}
|
||||
|
||||
if err := sq.UpdateLocalDependencies(cmd.Context(), flagParallel); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sq.RenderConfig(cmd.Context()); err != nil {
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
|
||||
username := "unknown"
|
||||
if value, err := util.NewCommand("git").Args("config", "user.name").Run(cmd.Context()); err == nil {
|
||||
username = strings.TrimSpace(value)
|
||||
} else if value, err := user.Current(); err == nil {
|
||||
username = strings.TrimSpace(value.Name)
|
||||
}
|
||||
if c.GetBool("build") {
|
||||
if err := sq.Build(cmd.Context(), c.GetStringSlice("build-args"), c.GetInt("parallel")); err != nil {
|
||||
return errors.Wrap(err, "failed to build units")
|
||||
}
|
||||
}
|
||||
|
||||
branch := ""
|
||||
if value, err := util.NewCommand("sh").Args("-c", "git describe --tags --exact-match 2> /dev/null || git symbolic-ref -q --short HEAD || git rev-parse --short HEAD").Run(cmd.Context()); err == nil {
|
||||
branch = strings.TrimSpace(value)
|
||||
}
|
||||
commit := ""
|
||||
if value, err := util.NewCommand("sh").Args("-c", "git rev-parse --short HEAD").Run(cmd.Context()); err == nil {
|
||||
commit = strings.TrimSpace(value)
|
||||
}
|
||||
if c.GetBool("push") {
|
||||
if err := sq.Push(cmd.Context(), c.GetStringSlice("push-args"), c.GetInt("parallel")); err != nil {
|
||||
return errors.Wrap(err, "failed to push units")
|
||||
}
|
||||
}
|
||||
|
||||
return sq.Up(cmd.Context(), helmArgs, username, version, commit, branch, flagParallel)
|
||||
},
|
||||
if err := sq.UpdateLocalDependencies(cmd.Context(), c.GetInt("parallel")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := "unknown"
|
||||
if value, err := util.NewCommand("git").Args("config", "user.name").Run(cmd.Context()); err == nil {
|
||||
username = strings.TrimSpace(value)
|
||||
} else if value, err := user.Current(); err == nil {
|
||||
username = strings.TrimSpace(value.Name)
|
||||
}
|
||||
|
||||
branch := ""
|
||||
if value, err := util.NewCommand("sh").Args("-c", "git describe --tags --exact-match 2> /dev/null || git symbolic-ref -q --short HEAD || git rev-parse --short HEAD").Run(cmd.Context()); err == nil {
|
||||
branch = strings.TrimSpace(value)
|
||||
}
|
||||
commit := ""
|
||||
if value, err := util.NewCommand("sh").Args("-c", "git rev-parse --short HEAD").Run(cmd.Context()); err == nil {
|
||||
commit = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
return sq.Up(cmd.Context(), helmArgs, username, version, commit, branch, c.GetInt("parallel"))
|
||||
},
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringP("namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
|
||||
_ = c.BindPFlag("namespace", flags.Lookup("namespace"))
|
||||
|
||||
flags.BoolP("build", "b", false, "builds or rebuilds units")
|
||||
_ = c.BindPFlag("build", flags.Lookup("build"))
|
||||
|
||||
flags.BoolP("push", "p", false, "pushes units to the registry")
|
||||
_ = c.BindPFlag("push", flags.Lookup("push"))
|
||||
|
||||
flags.Int("parallel", 1, "run command in parallel")
|
||||
_ = c.BindPFlag("parallel", flags.Lookup("parallel"))
|
||||
|
||||
flags.StringArray("build-args", nil, "additional docker buildx build args")
|
||||
_ = c.BindPFlag("build-args", flags.Lookup("build-args"))
|
||||
|
||||
flags.StringArray("push-args", nil, "additional docker push args")
|
||||
_ = c.BindPFlag("push-args", flags.Lookup("push-args"))
|
||||
|
||||
flags.StringSlice("tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
|
||||
_ = c.BindPFlag("tags", flags.Lookup("tags"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var version = "latest"
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "show version information",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(version)
|
||||
},
|
||||
func NewVersion(c *viper.Viper) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "show version information",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pterm.Println(version)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
36
internal/cmd/logger.go
Normal file
36
internal/cmd/logger.go
Normal file
@ -0,0 +1,36 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
pterm.Info.Prefix.Text = "⎈"
|
||||
pterm.Info.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
|
||||
pterm.Debug.Prefix.Text = "⛏︎"
|
||||
pterm.Debug.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
|
||||
pterm.Fatal.Prefix.Text = "⛔︎"
|
||||
pterm.Fatal.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
|
||||
pterm.Error.Prefix.Text = "⛌"
|
||||
pterm.Error.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
|
||||
pterm.Warning.Prefix.Text = "⚠"
|
||||
pterm.Warning.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
|
||||
pterm.Success.Prefix.Text = "✓"
|
||||
pterm.Success.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
|
||||
|
||||
if scope := os.Getenv("SQUADRON_SCOPE"); scope != "" {
|
||||
pterm.Info.Scope.Text = scope
|
||||
pterm.Debug.Scope.Text = scope
|
||||
pterm.Fatal.Scope.Text = scope
|
||||
pterm.Error.Scope.Text = scope
|
||||
pterm.Warning.Scope.Text = scope
|
||||
pterm.Success.Scope.Text = scope
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogger() *slog.Logger {
|
||||
return slog.New(NewPTermSlogHandler())
|
||||
}
|
||||
85
internal/cmd/ptermsloghandler.go
Normal file
85
internal/cmd/ptermsloghandler.go
Normal file
@ -0,0 +1,85 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
|
||||
type PTermSlogHandler struct {
|
||||
attrs []slog.Attr
|
||||
}
|
||||
|
||||
// Enabled returns true if the given level is enabled.
|
||||
func (s *PTermSlogHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
switch level {
|
||||
case slog.LevelDebug:
|
||||
return pterm.PrintDebugMessages
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Handle handles the given record.
|
||||
func (s *PTermSlogHandler) Handle(ctx context.Context, record slog.Record) error {
|
||||
level := record.Level
|
||||
message := record.Message
|
||||
|
||||
// Convert slog Attrs to a map.
|
||||
keyValsMap := make(map[string]any)
|
||||
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
keyValsMap[attr.Key] = attr.Value
|
||||
return true
|
||||
})
|
||||
|
||||
for _, attr := range s.attrs {
|
||||
keyValsMap[attr.Key] = attr.Value
|
||||
}
|
||||
|
||||
args := pterm.DefaultLogger.ArgsFromMap(keyValsMap)
|
||||
|
||||
// Wrapping args inside another slice to match [][]LoggerArgument
|
||||
argsWrapped := [][]pterm.LoggerArgument{args}
|
||||
|
||||
for _, arg := range argsWrapped {
|
||||
for _, attr := range arg {
|
||||
message += " " + attr.Key + ": " + fmt.Sprintf("%v", attr.Value)
|
||||
}
|
||||
}
|
||||
|
||||
switch level {
|
||||
case slog.LevelDebug:
|
||||
pterm.Debug.Println(message)
|
||||
case slog.LevelInfo:
|
||||
pterm.Info.Println(message)
|
||||
case slog.LevelWarn:
|
||||
pterm.Warning.Println(message)
|
||||
case slog.LevelError:
|
||||
pterm.Error.Println(message)
|
||||
default:
|
||||
pterm.Info.Println(message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithAttrs returns a new handler with the given attributes.
|
||||
func (s *PTermSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
newS := *s
|
||||
newS.attrs = attrs
|
||||
return &newS
|
||||
}
|
||||
|
||||
// WithGroup is not yet supported.
|
||||
func (s *PTermSlogHandler) WithGroup(name string) slog.Handler {
|
||||
// Grouping is not yet supported by pterm.
|
||||
return s
|
||||
}
|
||||
|
||||
// NewPTermSlogHandler returns a new logging handler that can be intrgrated with log/slog.
|
||||
func NewPTermSlogHandler() *PTermSlogHandler {
|
||||
return &PTermSlogHandler{}
|
||||
}
|
||||
@ -27,7 +27,7 @@ func (m Map[T]) Trim() {
|
||||
continue
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
switch val.Kind() { //nolint:exhaustive
|
||||
case reflect.Map, reflect.Slice:
|
||||
if val.Len() == 0 {
|
||||
delete(m, key)
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// JSONSchema represents the structure of a JSON schema
|
||||
@ -50,7 +52,7 @@ func (js *JSONSchema) SetSquadronUnitSchema(ctx context.Context, squardon, unit,
|
||||
if _, ok := defsMap[ref]; !ok {
|
||||
valuesMap, err := LoadMap(ctx, url)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to load map: "+url)
|
||||
}
|
||||
delete(valuesMap, "$schema")
|
||||
js.ensure(defsMap, ref, valuesMap)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package jsonschema_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@ -17,11 +16,11 @@ func TestJSONSchema(t *testing.T) {
|
||||
|
||||
// Create the JSONSchema object
|
||||
js := jsonschema.New()
|
||||
err := js.LoadBaseSchema(context.TODO(), baseURL)
|
||||
err := js.LoadBaseSchema(t.Context(), baseURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Override the base schema
|
||||
err = js.SetSquadronUnitSchema(context.TODO(), "site", "namespace", overrideURL)
|
||||
err = js.SetSquadronUnitSchema(t.Context(), "site", "namespace", overrideURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Print the resulting schema
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
|
||||
// LoadMap fetches the JSON schema from a given URL
|
||||
@ -15,6 +17,7 @@ func LoadMap(ctx context.Context, url string) (map[string]any, error) {
|
||||
var body []byte
|
||||
|
||||
if strings.HasPrefix(url, "http") {
|
||||
pterm.Debug.Printfln("Loading map from %s", url)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package jsonschema_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/squadron/internal/jsonschema"
|
||||
@ -10,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestLoadMap(t *testing.T) {
|
||||
actual, err := jsonschema.LoadMap(context.TODO(), "https://raw.githubusercontent.com/foomo/squadron/refs/heads/main/squadron.schema.json")
|
||||
actual, err := jsonschema.LoadMap(t.Context(), "https://raw.githubusercontent.com/foomo/squadron/refs/heads/main/squadron.schema.json")
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, actual)
|
||||
assert.Equal(t, "https://github.com/foomo/squadron/internal/config/config", actual["$id"])
|
||||
|
||||
@ -16,7 +16,7 @@ func Snapshot(t *testing.T, name, yaml string) {
|
||||
if *UpdateFlag || snapshot == "" {
|
||||
writeSnapshot(t, name, yaml)
|
||||
}
|
||||
assert.Equal(t, snapshot, yaml)
|
||||
assert.YAMLEq(t, snapshot, yaml)
|
||||
}
|
||||
|
||||
// writeSnapshot updates the snapshot file for a given test t.
|
||||
|
||||
@ -7,8 +7,8 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Cmd struct {
|
||||
@ -126,19 +126,26 @@ func (c *Cmd) Run(ctx context.Context) (string, error) {
|
||||
if c.cwd != "" {
|
||||
cmd.Dir = c.cwd
|
||||
}
|
||||
pterm.Debug.Printfln("executing %s", cmd.String())
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
traceWriter := logrus.StandardLogger().WriterLevel(logrus.TraceLevel)
|
||||
|
||||
if c.stdin != nil {
|
||||
cmd.Stdin = c.stdin
|
||||
}
|
||||
cmd.Stdout = io.MultiWriter(append(c.stdoutWriters, &stdout, traceWriter)...)
|
||||
cmd.Stderr = io.MultiWriter(append(c.stderrWriters, &stderr, traceWriter)...)
|
||||
|
||||
if value := PTermSpinnerFromContext(ctx); value != nil {
|
||||
c.stdoutWriters = append(c.stdoutWriters, value)
|
||||
c.stderrWriters = append(c.stderrWriters, value)
|
||||
}
|
||||
|
||||
cmd.Stdout = io.MultiWriter(append(c.stdoutWriters, &stdout)...)
|
||||
cmd.Stderr = io.MultiWriter(append(c.stderrWriters, &stderr)...)
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to execute: "+cmd.String())
|
||||
}
|
||||
return stdout.String() + stderr.String(), err
|
||||
}
|
||||
|
||||
|
||||
32
internal/util/errors.go
Normal file
32
internal/util/errors.go
Normal file
@ -0,0 +1,32 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
|
||||
func SprintError(err error) string {
|
||||
var ret string
|
||||
prefix := "Error: "
|
||||
if pterm.PrintDebugMessages {
|
||||
return fmt.Sprintf("%+v", err) + "\n"
|
||||
}
|
||||
|
||||
for {
|
||||
w := errors.Unwrap(err)
|
||||
if w == nil {
|
||||
ret += prefix + err.Error() + "\n"
|
||||
break
|
||||
}
|
||||
if err.Error() != w.Error() {
|
||||
ret += prefix + strings.TrimSuffix(err.Error(), ": "+w.Error()) + "\n"
|
||||
prefix = "↪ "
|
||||
}
|
||||
err = w
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(ret, "\n")
|
||||
}
|
||||
22
internal/util/errors_test.go
Normal file
22
internal/util/errors_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/squadron/internal/util"
|
||||
errorsx "github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSprintError(t *testing.T) {
|
||||
err := errors.New("test error")
|
||||
err = errorsx.Wrap(err, "1 wrap")
|
||||
err = errorsx.WithMessage(err, "2 with message")
|
||||
|
||||
ret := util.SprintError(err)
|
||||
|
||||
t.Log(ret)
|
||||
assert.Len(t, strings.Split(ret, "\n"), 3)
|
||||
}
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
k8s "k8s.io/api/apps/v1"
|
||||
)
|
||||
|
||||
@ -45,7 +46,7 @@ func (c KubeCmd) GetMostRecentPodBySelectors(ctx context.Context, selectors map[
|
||||
if len(pods) > 0 {
|
||||
return pods[len(pods)-1], nil
|
||||
}
|
||||
return "", fmt.Errorf("no pods found")
|
||||
return "", errors.New("no pods found")
|
||||
}
|
||||
|
||||
func (c KubeCmd) WaitForPodState(pod, condition, timeout string) *Cmd {
|
||||
|
||||
39
internal/util/ptermmultiprinter.go
Normal file
39
internal/util/ptermmultiprinter.go
Normal file
@ -0,0 +1,39 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
|
||||
type PTermMultiPrinter struct {
|
||||
printer *pterm.MultiPrinter
|
||||
}
|
||||
|
||||
func MustNewPTermMultiPrinter() *PTermMultiPrinter {
|
||||
printer, err := NewPTermMultiPrinter()
|
||||
if err != nil {
|
||||
pterm.Fatal.Println(err)
|
||||
}
|
||||
return printer
|
||||
}
|
||||
|
||||
func NewPTermMultiPrinter() (*PTermMultiPrinter, error) {
|
||||
printer, err := pterm.DefaultMultiPrinter.WithWriter(os.Stdout).Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PTermMultiPrinter{printer: printer}, nil
|
||||
}
|
||||
|
||||
func (s *PTermMultiPrinter) NewSpinner(prefix string) *PTermSpinner {
|
||||
return NewPTermSpinner(s.printer.NewWriter(), prefix)
|
||||
}
|
||||
|
||||
func (s *PTermMultiPrinter) Stop() {
|
||||
if s.printer.IsActive {
|
||||
if _, err := s.printer.Stop(); err != nil {
|
||||
pterm.Fatal.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
97
internal/util/ptermspinner.go
Normal file
97
internal/util/ptermspinner.go
Normal file
@ -0,0 +1,97 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const contextKeyPTermSpinner contextKey = "PtermSpinner"
|
||||
|
||||
type PTermSpinner struct {
|
||||
printer *pterm.SpinnerPrinter
|
||||
prefix string
|
||||
stopped bool
|
||||
start time.Time
|
||||
log []string
|
||||
}
|
||||
|
||||
func NewPTermSpinner(writer io.Writer, prefix string) *PTermSpinner {
|
||||
return &PTermSpinner{
|
||||
printer: pterm.DefaultSpinner.WithWriter(writer).
|
||||
WithDelay(500*time.Millisecond).
|
||||
WithSequence("▀ ", " ▀ ", " ▄ ", "▄ ").
|
||||
WithShowTimer(false),
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
func PTermSpinnerFromContext(ctx context.Context) *PTermSpinner {
|
||||
if value, ok := ctx.Value(contextKeyPTermSpinner).(*PTermSpinner); ok {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PTermSpinner) Inject(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, contextKeyPTermSpinner, s)
|
||||
}
|
||||
|
||||
func (s *PTermSpinner) Start(message ...string) {
|
||||
var err error
|
||||
if s.printer, err = s.printer.Start(s.message(message...)); err != nil {
|
||||
pterm.Fatal.Println(err)
|
||||
}
|
||||
s.start = time.Now()
|
||||
}
|
||||
|
||||
func (s *PTermSpinner) Info(message ...string) {
|
||||
s.stopped = true
|
||||
s.printer.Info(s.message(message...))
|
||||
}
|
||||
|
||||
func (s *PTermSpinner) Warning(message ...string) {
|
||||
s.stopped = true
|
||||
s.printer.Warning(s.message(message...))
|
||||
}
|
||||
|
||||
func (s *PTermSpinner) Fail(message ...string) {
|
||||
s.stopped = true
|
||||
s.printer.Fail(s.message(message...))
|
||||
}
|
||||
|
||||
func (s *PTermSpinner) Success(message ...string) {
|
||||
s.stopped = true
|
||||
s.printer.Success(s.message(message...))
|
||||
}
|
||||
|
||||
func (s *PTermSpinner) Write(p []byte) (int, error) {
|
||||
var lines []string
|
||||
for _, line := range strings.Split(string(p), "\n") {
|
||||
if line := strings.TrimSpace(line); len(line) > 0 {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
s.log = append(s.log, lines...)
|
||||
// s.printer.UpdateText(s.message())
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (s *PTermSpinner) message(message ...string) string {
|
||||
msg := s.prefix
|
||||
if !s.start.IsZero() && s.stopped {
|
||||
msg += " ⏱ " + time.Since(s.start).Round(0).String()
|
||||
}
|
||||
if value := strings.Join(message, " "); len(value) > 0 {
|
||||
msg += "\n" + value
|
||||
}
|
||||
if pterm.PrintDebugMessages {
|
||||
msg += "\n" + strings.Join(s.log, "\n")
|
||||
}
|
||||
return strings.TrimSpace(strings.ReplaceAll(msg, "\n", "\n "))
|
||||
}
|
||||
20
internal/util/ptermwriter.go
Normal file
20
internal/util/ptermwriter.go
Normal file
@ -0,0 +1,20 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
|
||||
type PTermWriter struct {
|
||||
printer pterm.PrefixPrinter
|
||||
}
|
||||
|
||||
func NewPTermWriter(printer pterm.PrefixPrinter) *PTermWriter {
|
||||
return &PTermWriter{
|
||||
printer: printer,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PTermWriter) Write(b []byte) (int, error) {
|
||||
p.printer.Println(string(b))
|
||||
return len(b), nil
|
||||
}
|
||||
462
squadron.go
462
squadron.go
@ -5,13 +5,11 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/squadron/internal/config"
|
||||
"github.com/foomo/squadron/internal/jsonschema"
|
||||
@ -38,6 +36,13 @@ type Squadron struct {
|
||||
c config.Config
|
||||
}
|
||||
|
||||
type statusDescription struct {
|
||||
ManagedBy string `json:"managedBy,omitempty"`
|
||||
DeployedBy string `json:"deployedBy,omitempty"`
|
||||
GitCommit string `json:"gitCommit,omitempty"`
|
||||
GitBranch string `json:"gitBranch,omitempty"`
|
||||
}
|
||||
|
||||
func New(basePath, namespace string, files []string) *Squadron {
|
||||
return &Squadron{
|
||||
basePath: basePath,
|
||||
@ -71,8 +76,7 @@ func (sq *Squadron) ConfigYAML() string {
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
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└ "))
|
||||
pterm.Debug.Println(strings.Join(append([]string{"merging config files"}, sq.files...), "\n└ "))
|
||||
|
||||
mergedFiles, err := conflate.FromFiles(sq.files...)
|
||||
if err != nil {
|
||||
@ -83,10 +87,12 @@ func (sq *Squadron) MergeConfigFiles(ctx context.Context) error {
|
||||
return errors.Wrap(err, "failed to marshal yaml")
|
||||
}
|
||||
if err := yaml.Unmarshal(fileBytes, &sq.c); err != nil {
|
||||
pterm.Error.Println(string(fileBytes))
|
||||
return err
|
||||
}
|
||||
if sq.c.Version != config.Version {
|
||||
return errors.New("Please upgrade your YAML definition to: " + config.Version)
|
||||
pterm.Debug.Println(string(fileBytes))
|
||||
return errors.New("Please upgrade your YAML definition to from '" + sq.c.Version + "' to '" + config.Version + "'")
|
||||
}
|
||||
|
||||
sq.c.Trim(ctx)
|
||||
@ -146,12 +152,10 @@ func (sq *Squadron) FilterConfig(ctx context.Context, squadron string, units, ta
|
||||
}
|
||||
|
||||
func (sq *Squadron) RenderConfig(ctx context.Context) error {
|
||||
pterm.Debug.Println("rendering config")
|
||||
|
||||
var tv templatex.Vars
|
||||
var vars map[string]any
|
||||
if err := yaml.Unmarshal([]byte(sq.config), &vars); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to render config")
|
||||
}
|
||||
// execute again with loaded template vars
|
||||
tv = templatex.Vars{}
|
||||
@ -168,23 +172,18 @@ func (sq *Squadron) RenderConfig(ctx context.Context) error {
|
||||
tv.Add("Squadron", value)
|
||||
}
|
||||
|
||||
// execute without errors to get existing values
|
||||
pterm.Debug.Println("executing file template")
|
||||
// pterm.Debug.Println(sq.config)
|
||||
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")
|
||||
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(out2, &vars); err != nil {
|
||||
fmt.Print(util.Highlight(string(out2)))
|
||||
return errors.Wrap(err, "failed to unmarshal vars")
|
||||
@ -205,14 +204,12 @@ func (sq *Squadron) RenderConfig(ctx context.Context) error {
|
||||
tv.Add("Squadron", value)
|
||||
}
|
||||
|
||||
pterm.Debug.Println("executing file template")
|
||||
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(out3, &sq.c); err != nil {
|
||||
fmt.Print(util.Highlight(string(out3)))
|
||||
return errors.Wrap(err, "failed to unmarshal vars")
|
||||
@ -227,30 +224,52 @@ func (sq *Squadron) Push(ctx context.Context, pushArgs []string, parallel int) e
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
wg.SetLimit(parallel)
|
||||
|
||||
printer := util.MustNewPTermMultiPrinter()
|
||||
defer printer.Stop()
|
||||
|
||||
type one struct {
|
||||
spinner *util.PTermSpinner
|
||||
squadron string
|
||||
unit string
|
||||
item config.Build
|
||||
}
|
||||
var all []one
|
||||
|
||||
_ = 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\n└ %s:%s", id, build.Image, build.Tag)
|
||||
if out, err := build.PushImage(ctx, key, k, pushArgs); err != nil {
|
||||
pterm.Error.Printfln("Push | %s ⏱ %s\n└ %s:%s", id, time.Since(start).Round(time.Millisecond), build.Image, build.Tag)
|
||||
return errors.Wrap(err, out)
|
||||
}
|
||||
pterm.Success.Printfln("Push | %s ⏱ %s\n└ %s:%s", id, time.Since(start).Round(time.Millisecond), build.Image, build.Tag)
|
||||
return nil
|
||||
spinner := printer.NewSpinner(fmt.Sprintf("🚚 | %s/%s.%s (%s:%s)", key, k, name, build.Image, build.Tag))
|
||||
all = append(all, one{
|
||||
spinner: spinner,
|
||||
squadron: key,
|
||||
unit: k,
|
||||
item: build,
|
||||
})
|
||||
spinner.Start()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
for _, a := range all {
|
||||
wg.Go(func() error {
|
||||
ctx := a.spinner.Inject(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
a.spinner.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if out, err := a.item.PushImage(ctx, a.squadron, a.unit, pushArgs); err != nil {
|
||||
a.spinner.Fail(out)
|
||||
return err
|
||||
}
|
||||
|
||||
a.spinner.Success()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
@ -258,20 +277,27 @@ func (sq *Squadron) BuildDependencies(ctx context.Context, buildArgs []string, p
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
wg.SetLimit(parallel)
|
||||
|
||||
printer := util.MustNewPTermMultiPrinter()
|
||||
defer printer.Stop()
|
||||
|
||||
dependencies := sq.c.BuildDependencies(ctx)
|
||||
for name, build := range dependencies {
|
||||
wg.Go(func() error {
|
||||
spinner := printer.NewSpinner(fmt.Sprintf("📦 | %s (%s:%s)", name, build.Image, build.Tag))
|
||||
spinner.Start()
|
||||
|
||||
ctx := spinner.Inject(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
spinner.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
pterm.Info.Printfln("Build | %s\n└ %s:%s", name, build.Image, build.Tag)
|
||||
if out, err := build.Build(ctx, "", "", buildArgs); err != nil {
|
||||
pterm.Error.Printfln("Build | %s ⏱ %s\n└ %s:%s", name, time.Since(start).Round(time.Millisecond), build.Image, build.Tag)
|
||||
return errors.Wrap(err, out)
|
||||
spinner.Fail(out)
|
||||
return err
|
||||
}
|
||||
pterm.Success.Printfln("Build | %s ⏱ %s\n└ %s:%s", name, time.Since(start).Round(time.Millisecond), build.Image, build.Tag)
|
||||
|
||||
spinner.Success()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@ -287,30 +313,52 @@ func (sq *Squadron) Build(ctx context.Context, buildArgs []string, parallel int)
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
wg.SetLimit(parallel)
|
||||
|
||||
printer := util.MustNewPTermMultiPrinter()
|
||||
defer printer.Stop()
|
||||
|
||||
type one struct {
|
||||
spinner *util.PTermSpinner
|
||||
squadron string
|
||||
unit string
|
||||
item config.Build
|
||||
}
|
||||
var all []one
|
||||
|
||||
_ = 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\n└ %s:%s", id, build.Image, build.Tag)
|
||||
if out, err := build.Build(ctx, key, k, buildArgs); err != nil {
|
||||
pterm.Error.Printfln("Build | %s ⏱ %s\n└ %s:%s", id, time.Since(start).Round(time.Millisecond), build.Image, build.Tag)
|
||||
return errors.Wrap(err, out)
|
||||
}
|
||||
pterm.Success.Printfln("Build | %s ⏱ %s\n└ %s:%s", id, time.Since(start).Round(time.Millisecond), build.Image, build.Tag)
|
||||
return nil
|
||||
spinner := printer.NewSpinner(fmt.Sprintf("📦 | %s/%s.%s (%s:%s)", key, k, name, build.Image, build.Tag))
|
||||
all = append(all, one{
|
||||
spinner: spinner,
|
||||
squadron: key,
|
||||
unit: k,
|
||||
item: build,
|
||||
})
|
||||
spinner.Start()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
for _, a := range all {
|
||||
wg.Go(func() error {
|
||||
ctx := a.spinner.Inject(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
a.spinner.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if out, err := a.item.Build(ctx, a.squadron, a.unit, buildArgs); err != nil {
|
||||
a.spinner.Fail(out)
|
||||
return err
|
||||
}
|
||||
|
||||
a.spinner.Success()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
@ -318,33 +366,37 @@ func (sq *Squadron) Down(ctx context.Context, helmArgs []string, parallel int) e
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
wg.SetLimit(parallel)
|
||||
|
||||
printer := util.MustNewPTermMultiPrinter()
|
||||
defer printer.Stop()
|
||||
|
||||
_ = 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 {
|
||||
spinner := printer.NewSpinner(fmt.Sprintf("🗑️ | %s/%s", key, k))
|
||||
spinner.Start()
|
||||
|
||||
ctx := spinner.Inject(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
spinner.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
name := fmt.Sprintf("%s-%s", key, k)
|
||||
namespace, err := sq.Namespace(ctx, key, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdErr := bytes.NewBuffer([]byte{})
|
||||
pterm.Info.Printfln("Down | %s/%s", key, k)
|
||||
if out, err := util.NewHelmCommand().Args("uninstall", name).
|
||||
Stderr(stdErr).
|
||||
Stdout(os.Stdout).
|
||||
Args("--namespace", namespace).
|
||||
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)
|
||||
strings.TrimSpace(out) != fmt.Sprintf("Error: uninstall: Release not loaded: %s: release: not found", name) {
|
||||
spinner.Fail(out)
|
||||
return err
|
||||
}
|
||||
pterm.Success.Printfln("Down | %s/%s ⏱ %s", key, k, time.Since(start).Round(time.Millisecond))
|
||||
|
||||
spinner.Success()
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
@ -360,7 +412,6 @@ func (sq *Squadron) RenderSchema(ctx context.Context, baseSchema string) (string
|
||||
return "", errors.Wrap(err, "failed to load base schema")
|
||||
}
|
||||
|
||||
pterm.Debug.Println("rendering schema")
|
||||
if 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 err := ctx.Err(); err != nil {
|
||||
@ -374,22 +425,37 @@ func (sq *Squadron) RenderSchema(ctx context.Context, baseSchema string) (string
|
||||
return js.SetSquadronUnitSchema(ctx, key, k, v.Chart.Schema)
|
||||
})
|
||||
}); err != nil {
|
||||
return "", errors.Wrap(err, "failed to render schema")
|
||||
return "", err
|
||||
}
|
||||
|
||||
return js.PrettyString()
|
||||
}
|
||||
|
||||
func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) error {
|
||||
func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) (string, error) {
|
||||
var m sync.Mutex
|
||||
var ret bytes.Buffer
|
||||
write := func(b []byte) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
_, err := ret.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
wg.SetLimit(parallel)
|
||||
|
||||
printer := util.MustNewPTermMultiPrinter()
|
||||
defer printer.Stop()
|
||||
|
||||
_ = 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 {
|
||||
spinner := printer.NewSpinner(fmt.Sprintf("🔍 | %s/%s", key, k))
|
||||
spinner.Start()
|
||||
|
||||
ctx := spinner.Inject(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
spinner.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
@ -403,10 +469,10 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) e
|
||||
return err
|
||||
}
|
||||
|
||||
pterm.Debug.Printfln("running helm diff for: %s", k)
|
||||
manifest, err := exec.CommandContext(ctx, "helm", "get", "manifest", name, "--namespace", namespace).CombinedOutput()
|
||||
if err != nil && string(bytes.TrimSpace(manifest)) != errHelmReleaseNotFound {
|
||||
return errors.Wrap(err, string(manifest))
|
||||
spinner.Fail(string(manifest))
|
||||
return err
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "helm", "upgrade", name,
|
||||
"--install",
|
||||
@ -434,19 +500,20 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) e
|
||||
cmd.Args = append(cmd.Args, helmArgs...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, string(out))
|
||||
spinner.Fail(string(out))
|
||||
return err
|
||||
}
|
||||
|
||||
yamls1, err := yamldiff.Load(string(manifest))
|
||||
if err != nil {
|
||||
fmt.Print(util.Highlight(string(manifest)))
|
||||
spinner.Fail(string(manifest))
|
||||
return errors.Wrap(err, "failed to load yaml diff")
|
||||
}
|
||||
|
||||
outStr := strings.Split(string(out), "\n")
|
||||
yamls2, err := yamldiff.Load(strings.Join(outStr[10:], "\n"))
|
||||
if err != nil {
|
||||
fmt.Print(util.Highlight(string(out)))
|
||||
spinner.Fail(string(out))
|
||||
return errors.Wrap(err, "failed to load yaml diff")
|
||||
}
|
||||
|
||||
@ -455,25 +522,24 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) e
|
||||
res += diff.Dump() + " ---\n"
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
pterm.Info.Printfln("Diff | %s/%s", key, k)
|
||||
fmt.Print(util.Highlight(res))
|
||||
if err := write([]byte(res)); err != nil {
|
||||
spinner.Fail(res)
|
||||
return err
|
||||
}
|
||||
|
||||
spinner.Success()
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
return wg.Wait()
|
||||
}
|
||||
if err := wg.Wait(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
type statusDescription struct {
|
||||
ManagedBy string `json:"managedBy,omitempty"`
|
||||
DeployedBy string `json:"deployedBy,omitempty"`
|
||||
GitCommit string `json:"gitCommit,omitempty"`
|
||||
GitBranch string `json:"gitBranch,omitempty"`
|
||||
return ret.String(), nil
|
||||
}
|
||||
|
||||
func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int) error {
|
||||
@ -506,27 +572,46 @@ func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int)
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
wg.SetLimit(parallel)
|
||||
|
||||
printer := util.MustNewPTermMultiPrinter()
|
||||
defer printer.Stop()
|
||||
|
||||
_ = 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)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Errorf("failed to retrieve namsspace: %s/%s", key, k)
|
||||
}
|
||||
|
||||
stdErr := bytes.NewBuffer([]byte{})
|
||||
pterm.Debug.Printfln("running helm status for %s", name)
|
||||
if out, err := util.NewHelmCommand().Args("status", name).
|
||||
Stderr(stdErr).
|
||||
Args("--namespace", namespace, "--output", "json", "--show-desc").
|
||||
Args(helmArgs...).Run(ctx); err != nil && string(bytes.TrimSpace(stdErr.Bytes())) == errHelmReleaseNotFound {
|
||||
tbd = append(tbd, []string{name, "0", "not installed", "", ""})
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, out)
|
||||
} else if err := json.Unmarshal([]byte(out), &status); err != nil {
|
||||
return errors.Wrap(err, out)
|
||||
} else {
|
||||
wg.Go(func() error {
|
||||
spinner := printer.NewSpinner(fmt.Sprintf("📄 | %s/%s", key, k))
|
||||
spinner.Start()
|
||||
|
||||
ctx := spinner.Inject(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
spinner.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
stdErr := bytes.NewBuffer([]byte{})
|
||||
out, err := util.NewHelmCommand().Args("status", name).
|
||||
Stderr(stdErr).
|
||||
Args("--namespace", namespace, "--output", "json", "--show-desc").
|
||||
Args(helmArgs...).Run(ctx)
|
||||
|
||||
if err != nil && string(bytes.TrimSpace(stdErr.Bytes())) == errHelmReleaseNotFound {
|
||||
tbd = append(tbd, []string{name, "0", "not installed", "", ""})
|
||||
} else if err != nil {
|
||||
spinner.Fail(stdErr.String())
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(out), &status); err != nil {
|
||||
spinner.Fail(out)
|
||||
return errors.Errorf("failed to retrieve status: %s/%s", key, k)
|
||||
}
|
||||
|
||||
var notes []string
|
||||
lines := strings.Split(status.Info.Description, "\n")
|
||||
var statusDescription statusDescription
|
||||
@ -565,7 +650,11 @@ func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int)
|
||||
status.Info.LastDeployed,
|
||||
strings.Join(notes, "\n"),
|
||||
})
|
||||
}
|
||||
|
||||
spinner.Success()
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
@ -574,7 +663,13 @@ func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int)
|
||||
return err
|
||||
}
|
||||
|
||||
return pterm.DefaultTable.WithHasHeader().WithData(tbd).Render()
|
||||
out, err := pterm.DefaultTable.WithHasHeader().WithData(tbd).Srender()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pterm.Println(out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sq *Squadron) Rollback(ctx context.Context, revision string, helmArgs []string, parallel int) error {
|
||||
@ -585,6 +680,9 @@ func (sq *Squadron) Rollback(ctx context.Context, revision string, helmArgs []st
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
wg.SetLimit(parallel)
|
||||
|
||||
printer := util.MustNewPTermMultiPrinter()
|
||||
defer printer.Stop()
|
||||
|
||||
_ = 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)
|
||||
@ -593,17 +691,32 @@ func (sq *Squadron) Rollback(ctx context.Context, revision string, helmArgs []st
|
||||
return err
|
||||
}
|
||||
|
||||
stdErr := bytes.NewBuffer([]byte{})
|
||||
pterm.Debug.Printfln("running helm uninstall for: `%s`", name)
|
||||
if out, err := util.NewHelmCommand().Args("rollback", name).
|
||||
Stderr(stdErr).
|
||||
Stdout(os.Stdout).
|
||||
Args(helmArgs...).
|
||||
Args("--namespace", namespace).
|
||||
Run(ctx); err != nil &&
|
||||
string(bytes.TrimSpace(stdErr.Bytes())) != fmt.Sprintf("Error: uninstall: Release not loaded: %s: release: not found", name) {
|
||||
return errors.Wrap(err, out)
|
||||
}
|
||||
wg.Go(func() error {
|
||||
spinner := printer.NewSpinner(fmt.Sprintf("♻️ | %s/%s", key, k))
|
||||
spinner.Start()
|
||||
|
||||
ctx := spinner.Inject(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
spinner.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
stdErr := bytes.NewBuffer([]byte{})
|
||||
out, err := util.NewHelmCommand().Args("rollback", name).
|
||||
Stderr(stdErr).
|
||||
// Stdout(os.Stdout).
|
||||
Args(helmArgs...).
|
||||
Args("--namespace", namespace).
|
||||
Run(ctx)
|
||||
if err != nil &&
|
||||
string(bytes.TrimSpace(stdErr.Bytes())) != fmt.Sprintf("Error: uninstall: Release not loaded: %s: release: not found", name) {
|
||||
spinner.Fail(stdErr.String())
|
||||
return err
|
||||
}
|
||||
|
||||
spinner.Success(out)
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -664,61 +777,89 @@ func (sq *Squadron) Up(ctx context.Context, helmArgs []string, username, version
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
wg.SetLimit(parallel)
|
||||
|
||||
printer := util.MustNewPTermMultiPrinter()
|
||||
defer printer.Stop()
|
||||
type one struct {
|
||||
spinner *util.PTermSpinner
|
||||
squadron string
|
||||
unit string
|
||||
item *config.Unit
|
||||
}
|
||||
var all []one
|
||||
|
||||
_ = 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 {
|
||||
return err
|
||||
}
|
||||
valueBytes, err := v.ValuesYAML(sq.c.Global)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// install chart
|
||||
pterm.Debug.Printfln("running helm upgrade for %s", name)
|
||||
cmd := util.NewHelmCommand().
|
||||
Stdin(bytes.NewReader(valueBytes)).
|
||||
Stdout(os.Stdout).
|
||||
Args("upgrade", name, "--install").
|
||||
Args("--set", "global.foomo.squadron.name="+key).
|
||||
Args("--set", "global.foomo.squadron.unit="+k).
|
||||
Args("--description", string(description)).
|
||||
Args("--namespace", namespace).
|
||||
Args("--dependency-update").
|
||||
Args(v.PostRendererArgs()...).
|
||||
Args("--install").
|
||||
Args("--values", "-").
|
||||
Args(helmArgs...)
|
||||
|
||||
if strings.HasPrefix(v.Chart.Repository, "file://") {
|
||||
cmd.Args(path.Clean(strings.TrimPrefix(v.Chart.Repository, "file://")))
|
||||
} else {
|
||||
cmd.Args(v.Chart.Name)
|
||||
if v.Chart.Repository != "" {
|
||||
cmd.Args("--repo", v.Chart.Repository)
|
||||
}
|
||||
if v.Chart.Version != "" {
|
||||
cmd.Args("--version", v.Chart.Version)
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := cmd.Run(ctx); err != nil {
|
||||
return errors.Wrap(err, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
spinner := printer.NewSpinner(fmt.Sprintf("🚀 | %s/%s", key, k))
|
||||
all = append(all, one{
|
||||
spinner: spinner,
|
||||
squadron: key,
|
||||
unit: k,
|
||||
item: v,
|
||||
})
|
||||
spinner.Start()
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
for _, a := range all {
|
||||
wg.Go(func() error {
|
||||
ctx := a.spinner.Inject(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
a.spinner.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%s-%s", a.squadron, a.unit)
|
||||
namespace, err := sq.Namespace(ctx, a.squadron, a.unit)
|
||||
if err != nil {
|
||||
a.spinner.Fail(err.Error())
|
||||
return err
|
||||
}
|
||||
valueBytes, err := a.item.ValuesYAML(sq.c.Global)
|
||||
if err != nil {
|
||||
a.spinner.Fail(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// install chart
|
||||
cmd := util.NewHelmCommand().
|
||||
Stdin(bytes.NewReader(valueBytes)).
|
||||
// Stdout(os.Stdout).
|
||||
Args("upgrade", name, "--install").
|
||||
Args("--set", "global.foomo.squadron.name="+a.squadron).
|
||||
Args("--set", "global.foomo.squadron.unit="+a.unit).
|
||||
Args("--description", string(description)).
|
||||
Args("--namespace", namespace).
|
||||
Args("--dependency-update").
|
||||
Args(a.item.PostRendererArgs()...).
|
||||
Args("--install").
|
||||
Args("--values", "-").
|
||||
Args(helmArgs...)
|
||||
|
||||
if strings.HasPrefix(a.item.Chart.Repository, "file://") {
|
||||
cmd.Args(path.Clean(strings.TrimPrefix(a.item.Chart.Repository, "file://")))
|
||||
} else {
|
||||
cmd.Args(a.item.Chart.Name)
|
||||
if a.item.Chart.Repository != "" {
|
||||
cmd.Args("--repo", a.item.Chart.Repository)
|
||||
}
|
||||
if a.item.Chart.Version != "" {
|
||||
cmd.Args("--version", a.item.Chart.Version)
|
||||
}
|
||||
}
|
||||
|
||||
out, err := cmd.Run(ctx)
|
||||
if err != nil {
|
||||
a.spinner.Fail(out)
|
||||
return err
|
||||
}
|
||||
|
||||
a.spinner.Success()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
@ -735,26 +876,41 @@ func (sq *Squadron) Template(ctx context.Context, helmArgs []string, parallel in
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
wg.SetLimit(parallel)
|
||||
|
||||
printer := util.MustNewPTermMultiPrinter()
|
||||
defer printer.Stop()
|
||||
|
||||
_ = 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 {
|
||||
spinner := printer.NewSpinner(fmt.Sprintf("🧾 | %s/%s", key, k))
|
||||
spinner.Start()
|
||||
|
||||
ctx := spinner.Inject(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
spinner.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%s-%s", key, k)
|
||||
namespace, err := sq.Namespace(ctx, key, k)
|
||||
if err != nil {
|
||||
return err
|
||||
spinner.Fail(err.Error())
|
||||
return errors.Errorf("failed to retrieve namsspace: %s/%s", key, k)
|
||||
}
|
||||
|
||||
pterm.Debug.Printfln("running helm template for chart: %s", name)
|
||||
out, err := v.Template(ctx, name, key, k, namespace, sq.c.Global, helmArgs)
|
||||
if err != nil {
|
||||
spinner.Fail(string(out))
|
||||
return err
|
||||
}
|
||||
|
||||
return write(out)
|
||||
if err := write(out); err != nil {
|
||||
spinner.Fail(string(out))
|
||||
return err
|
||||
}
|
||||
|
||||
spinner.Success()
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package squadron_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
@ -102,7 +101,7 @@ func TestConfigSimpleSnapshot(t *testing.T) {
|
||||
func runTestConfig(t *testing.T, name string, files []string, squadronName string, unitNames, tags []string) {
|
||||
t.Helper()
|
||||
var cwd string
|
||||
ctx := context.TODO()
|
||||
ctx := t.Context()
|
||||
require.NoError(t, util.ValidatePath(".", &cwd))
|
||||
|
||||
for i, file := range files {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user