Compare commits

...

3 Commits

Author SHA1 Message Date
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
Jeff McCune
5862725bab builder: deprecate ExtractYAML, use cue embed instead
Easier to place the data, better supported in the ecosystem.
2025-01-02 18:53:10 -08:00
Jeff McCune
8660826b05 builder: protect LoadInstance with a mutex
CUE is not safe for concurrent access so we protect the main
LoadInstance function with a mutex lock.
2025-01-02 17:32:53 -08:00
6 changed files with 46 additions and 20 deletions

View File

@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
@@ -19,11 +20,15 @@ import (
"github.com/holos-run/holos/internal/util"
)
// cue context and loading is not safe for concurrent use.
var cueMutex sync.Mutex
// ExtractYAML extracts yaml encoded data from file paths. The data is unified
// into one [cue.Value]. If a path element is a directory, all files in the
// directory are loaded non-recursively.
//
// Attribution: https://github.com/cue-lang/cue/issues/3504
// Deprecated: Use cue embed instead.
func ExtractYAML(ctxt *cue.Context, filepaths []string) (cue.Value, error) {
value := ctxt.CompileString("")
files := make([]string, 0, 10*len(filepaths))
@@ -67,6 +72,8 @@ func ExtractYAML(ctxt *cue.Context, filepaths []string) (cue.Value, error) {
// extracted data values are unified with the platform configuration [cue.Value]
// in the returned [Instance].
func LoadInstance(path string, filepaths []string, tags []string) (*Instance, error) {
cueMutex.Lock()
defer cueMutex.Unlock()
root, leaf, err := util.FindRootLeaf(path)
if err != nil {
return nil, errors.Wrap(err)
@@ -89,7 +96,6 @@ func LoadInstance(path string, filepaths []string, tags []string) (*Instance, er
if err != nil {
return nil, errors.Wrap(err)
}
// TODO: https://cuelang.org/docs/howto/place-data-go-api/
value = value.Unify(values[0])
inst := &Instance{

View File

@@ -16,7 +16,7 @@ import (
// platform.
type PlatformOpts struct {
Fn func(context.Context, int, holos.Component) error
Selector holos.Selector
Selectors holos.Selectors
Concurrency int
InfoEnabled bool
}
@@ -31,7 +31,7 @@ type Platform struct {
func (p *Platform) Build(ctx context.Context, opts PlatformOpts) error {
limit := max(opts.Concurrency, 1)
parentStart := time.Now()
components := p.Select(opts.Selector)
components := p.Select(opts.Selectors...)
total := len(components)
g, ctx := errgroup.WithContext(ctx)

View File

@@ -45,8 +45,8 @@ func newPlatform(cfg *holos.Config, feature holos.Flagger) *cobra.Command {
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 selector holos.Selector
cmd.Flags().VarP(&selector, "selector", "l", "label selector (e.g. label==string,label!=string)")
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)
@@ -75,7 +75,7 @@ func newPlatform(cfg *holos.Config, feature holos.Flagger) *cobra.Command {
}
opts := builder.PlatformOpts{
Fn: makeComponentRenderFunc(cmd.ErrOrStderr(), prefixArgs, tagMap.Tags()),
Selector: selector,
Selectors: selectors,
Concurrency: concurrency,
InfoEnabled: true,
}

View File

@@ -70,8 +70,8 @@ func newShowBuildPlanCmd() (cmd *cobra.Command) {
cmd.Flags().Var(&extractYAMLs, "extract-yaml", "data file paths to extract and unify with the platform config")
var format string
cmd.Flags().StringVar(&format, "format", "yaml", "yaml or json format")
var selector holos.Selector
cmd.Flags().VarP(&selector, "selector", "l", "label selector (e.g. label==string,label!=string)")
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", "set the value of a cue @tag field from a key=value pair")
var concurrency int
@@ -102,7 +102,7 @@ func newShowBuildPlanCmd() (cmd *cobra.Command) {
platformOpts := builder.PlatformOpts{
Fn: makeBuildFunc(encoder, buildPlanOpts),
Selector: selector,
Selectors: selectors,
Concurrency: concurrency,
}

View File

@@ -107,6 +107,28 @@ func (e *EnvFlagger) Flag(name feature) bool {
type Labels map[string]string
type Selectors []Selector
// String implements the flag.Value interface.
func (s *Selectors) String() string {
return fmt.Sprint(*s)
}
// Type implements the pflag.Value interface and describes the type.
func (s *Selectors) Type() string {
return "selectors"
}
// Set implements the flag.Value interface.
func (s *Selectors) Set(value string) error {
selector := Selector{}
if err := selector.Set(value); err != nil {
return err
}
*s = append(*s, selector)
return nil
}
type Selector struct {
Positive map[string]string
Negative map[string]string
@@ -118,14 +140,9 @@ func (s *Selector) IsSelected(labels Labels) bool {
return true // Nil selector selects everything
}
if len(s.Positive) == 0 && len(s.Negative) == 0 {
return true // Empty selector selects everything
}
// Check positive matches
for k, v := range s.Positive {
val, ok := labels[k]
if !ok || v != val {
if val, ok := labels[k]; !ok || v != val {
return false
}
}
@@ -251,15 +268,18 @@ func (y *yamlEncoder) Close() error {
return errors.Wrap(y.enc.Close())
}
// IsSelected returns true if all selectors select the given labels or no
// IsSelected returns true if any one selector selects the given labels or no
// selectors are given.
func IsSelected(labels Labels, selectors ...Selector) bool {
if len(selectors) == 0 {
return true
}
for _, selector := range selectors {
if !selector.IsSelected(labels) {
return false
if selector.IsSelected(labels) {
return true
}
}
return true
return false
}
type orderedEncoder struct {

View File

@@ -1 +1 @@
2
4