Files
holos/internal/cli/render/render.go
Jeff McCune 6a60b613ff render: fix selectors (#394)
Without this patch selectors don't work as expected.  This patch
fixes selectors such that each --selector flag value configures one
selector containing multiple positive or negative label matchers.

Result:

Render build plans for cluster dev or cluster test.  Note the use of two
flags indicating logical OR.

    holos render platform --selector cluster=test --selector cluster=dev
    rendered external-secrets for cluster test in 299.897542ms
    rendered external-secrets for cluster dev in 299.9225ms
    rendered external-secrets-crds for cluster test in 667.6075ms
    rendered external-secrets-crds for cluster dev in 708.126541ms
    rendered platform in 708.795625ms

Render build plans for prod clusters that are not customer facing.  Note
the use of one selector with comma separated labels.

    holos render platform --selector "tier=prod,scope!=customer"
2025-01-08 21:09:00 -08:00

177 lines
5.3 KiB
Go

package render
import (
"context"
"fmt"
"io"
"runtime"
"github.com/holos-run/holos/internal/builder"
"github.com/holos-run/holos/internal/cli/command"
"github.com/holos-run/holos/internal/client"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/holos"
"github.com/holos-run/holos/internal/logger"
"github.com/holos-run/holos/internal/util"
"github.com/spf13/cobra"
)
const tagHelp = "set the value of a cue @tag field in the form key [ = value ]"
func New(cfg *holos.Config, feature holos.Flagger) *cobra.Command {
cmd := command.New("render")
cmd.Args = cobra.NoArgs
cmd.Short = "render platforms and components to manifest files"
cmd.AddCommand(newPlatform(cfg, feature))
cmd.AddCommand(newComponent(cfg, feature))
return cmd
}
func newPlatform(cfg *holos.Config, feature holos.Flagger) *cobra.Command {
cmd := command.New("platform")
cmd.Args = cobra.MaximumNArgs(1)
cmd.Example = "holos render platform"
cmd.Short = "render an entire platform"
config := client.NewConfig(cfg)
if feature.Flag(holos.ClientFeature) {
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
}
var concurrency int
cmd.Flags().IntVar(&concurrency, "concurrency", runtime.NumCPU(), "number of components to render concurrently")
var platform string
cmd.Flags().StringVar(&platform, "platform", "./platform", "platform directory path")
var extractYAMLs holos.StringSlice
cmd.Flags().Var(&extractYAMLs, "extract-yaml", "data file paths to extract and unify with the platform config")
var selectors holos.Selectors
cmd.Flags().VarP(&selectors, "selector", "l", "label selector (e.g. label==string,label!=string)")
tagMap := make(holos.TagMap)
cmd.Flags().VarP(&tagMap, "inject", "t", tagHelp)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
ctx := cmd.Root().Context()
log := logger.FromContext(ctx)
if len(args) > 0 {
platform = args[0]
msg := "deprecated: %s, use the --platform flag instead"
log.WarnContext(ctx, fmt.Sprintf(msg, platform))
}
inst, err := builder.LoadInstance(platform, extractYAMLs, tagMap.Tags())
if err != nil {
return errors.Wrap(err)
}
platform, err := builder.LoadPlatform(inst)
if err != nil {
return errors.Wrap(err)
}
prefixArgs := []string{
"--log-level", cfg.LogConfig().Level(),
"--log-format", cfg.LogConfig().Format(),
}
opts := builder.PlatformOpts{
Fn: makeComponentRenderFunc(cmd.ErrOrStderr(), prefixArgs, tagMap.Tags()),
Selectors: selectors,
Concurrency: concurrency,
InfoEnabled: true,
}
if err := platform.Build(ctx, opts); err != nil {
return errors.Wrap(err)
}
return nil
}
return cmd
}
// New returns the component subcommand for the render command
func newComponent(cfg *holos.Config, feature holos.Flagger) *cobra.Command {
cmd := command.New("component DIRECTORY")
cmd.Args = cobra.ExactArgs(1)
cmd.Short = "render a platform component"
cmd.Example = " holos render component --inject holos_cluster=aws2 ./components/monitoring/kube-prometheus-stack"
cmd.Flags().AddGoFlagSet(cfg.WriteFlagSet())
cmd.Flags().AddGoFlagSet(cfg.ClusterFlagSet())
config := client.NewConfig(cfg)
if feature.Flag(holos.ClientFeature) {
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
}
tagMap := make(holos.TagMap)
cmd.Flags().VarP(&tagMap, "inject", "t", tagHelp)
var concurrency int
cmd.Flags().IntVar(&concurrency, "concurrency", runtime.NumCPU(), "number of concurrent build steps")
var extractYAMLs holos.StringSlice
cmd.Flags().Var(&extractYAMLs, "extract-yaml", "data file paths to extract and unify with the platform config")
cmd.RunE = func(cmd *cobra.Command, args []string) error {
ctx := cmd.Root().Context()
path := args[0]
inst, err := builder.LoadInstance(path, extractYAMLs, tagMap.Tags())
if err != nil {
return errors.Wrap(err)
}
opts := holos.NewBuildOpts(path)
opts.Stderr = cmd.ErrOrStderr()
opts.Concurrency = concurrency
opts.WriteTo = cfg.WriteTo()
bp, err := builder.LoadBuildPlan(inst, opts)
if err != nil {
return errors.Wrap(err)
}
if err := bp.Build(ctx); err != nil {
return errors.Wrap(err)
}
return nil
}
return cmd
}
func makeComponentRenderFunc(w io.Writer, prefixArgs, cliTags []string) func(context.Context, int, holos.Component) error {
return func(ctx context.Context, idx int, component holos.Component) error {
select {
case <-ctx.Done():
return errors.Wrap(ctx.Err())
default:
tags, err := component.Tags()
if err != nil {
return errors.Wrap(err)
}
filepaths, err := component.ExtractYAML()
if err != nil {
return errors.Wrap(err)
}
args := make([]string, 0, 10+len(prefixArgs)+(len(tags)*2+len(filepaths)*2))
args = append(args, prefixArgs...)
args = append(args, "render", "component")
for _, tag := range cliTags {
args = append(args, "--inject", tag)
}
for _, tag := range tags {
args = append(args, "--inject", tag)
}
for _, path := range filepaths {
args = append(args, "--extract-yaml", path)
}
args = append(args, component.Path())
if _, err := util.RunCmdA(ctx, w, "holos", args...); err != nil {
return errors.Format("could not render component: %w", err)
}
}
return nil
}
}