From 864d7d442b4a18e6ecacd926c94ef93ec25abe76 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 20 Nov 2024 10:16:30 -0800 Subject: [PATCH] build: pass platform component labels and annotations to BuildPlan Without this patch the BuildPlan resulting from a Platform that has components with labels and annotations does not have the labels or annotations of the source component. Holos should copy the labels and annotations defined on each of the Platform.spec.components to the resulting BuildPlan so end users can see clearly where a BuildPlan originated from, and filter with selectors the intermediate output BuildPlan the same way we filter with selectors the original Platform spec components list. Result: ``` holos init platform v1alpha5 --force holos show buildplans | head ``` ```yaml kind: BuildPlan apiVersion: v1alpha5 metadata: name: podinfo labels: app.holos.run/cluster: local app.holos.run/name: podinfo annotations: app.holos.run/description: podinfo for cluster local ``` --- api/author/v1alpha5/definitions.go | 5 ++ .../v1alpha5/issues/helm-pull-errors.txt | 2 +- .../tests/v1alpha5/issues/holos-show.txt | 52 +++++++++++++++++++ doc/md/api/author.md | 5 ++ internal/builder/v1alpha5/builder.go | 29 +++++++++-- internal/cli/render/render.go | 5 +- internal/cli/show.go | 6 ++- .../author/v1alpha5/definitions_go_gen.cue | 6 +++ .../holos/api/author/v1alpha5/definitions.cue | 16 ++++++ .../generate/platforms/v1alpha5/schema.cue | 10 ++++ internal/generate/platforms/v1alpha5/tags.cue | 24 ++++++++- internal/holos/interface.go | 2 +- 12 files changed, 154 insertions(+), 8 deletions(-) diff --git a/api/author/v1alpha5/definitions.go b/api/author/v1alpha5/definitions.go index eb976b80..58cb3a94 100644 --- a/api/author/v1alpha5/definitions.go +++ b/api/author/v1alpha5/definitions.go @@ -46,6 +46,11 @@ type ComponentConfig struct { // Name represents the BuildPlan metadata.name field. Used to construct the // fully rendered manifest file path. Name string + // Labels represent the BuildPlan metadata.labels field. + Labels map[string]string + // Annotations represent the BuildPlan metadata.annotations field. + Annotations map[string]string + // Path represents the path to the component producing the BuildPlan. Path string // Parameters are useful to reuse a component with various parameters. diff --git a/cmd/holos/tests/v1alpha5/issues/helm-pull-errors.txt b/cmd/holos/tests/v1alpha5/issues/helm-pull-errors.txt index 5aea3fe4..d4e0c0e3 100644 --- a/cmd/holos/tests/v1alpha5/issues/helm-pull-errors.txt +++ b/cmd/holos/tests/v1alpha5/issues/helm-pull-errors.txt @@ -6,7 +6,7 @@ chmod 755 bin/helm # Initialize the platform exec holos init platform v1alpha5 --force # when helm update returns an error -! exec holos render platform ./platform +! exec holos render platform # holos should log the helm error to stderr stderr 'Error: chart "podinfo" matching 0.0.0 not found in podinfo index' -- bin/helm -- diff --git a/cmd/holos/tests/v1alpha5/issues/holos-show.txt b/cmd/holos/tests/v1alpha5/issues/holos-show.txt index 3eb9ac97..528f88d4 100644 --- a/cmd/holos/tests/v1alpha5/issues/holos-show.txt +++ b/cmd/holos/tests/v1alpha5/issues/holos-show.txt @@ -87,6 +87,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty1 + labels: + app.holos.run/name: empty1-label + annotations: + app.holos.run/description: empty1-annotation empty test case spec: artifacts: - artifact: components/empty1/empty1.gen.yaml @@ -112,6 +116,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty2 + labels: + app.holos.run/name: empty2-label + annotations: + app.holos.run/description: empty2-annotation empty test case spec: artifacts: - artifact: components/empty2/empty2.gen.yaml @@ -137,6 +145,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty3 + labels: + app.holos.run/name: empty3-label + annotations: + app.holos.run/description: empty3-annotation empty test case spec: artifacts: - artifact: components/empty3/empty3.gen.yaml @@ -162,6 +174,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty4 + labels: + app.holos.run/name: empty4-label + annotations: + app.holos.run/description: empty4-annotation empty test case spec: artifacts: - artifact: components/empty4/empty4.gen.yaml @@ -187,6 +203,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty1 + labels: + app.holos.run/name: empty1-label + annotations: + app.holos.run/description: empty1-annotation empty test case spec: artifacts: - artifact: components/empty1/empty1.gen.yaml @@ -212,6 +232,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty2 + labels: + app.holos.run/name: empty2-label + annotations: + app.holos.run/description: empty2-annotation empty test case spec: artifacts: - artifact: components/empty2/empty2.gen.yaml @@ -237,6 +261,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty1 + labels: + app.holos.run/name: empty1-label + annotations: + app.holos.run/description: empty1-annotation empty test case spec: artifacts: - artifact: components/empty1/empty1.gen.yaml @@ -262,6 +290,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty2 + labels: + app.holos.run/name: empty2-label + annotations: + app.holos.run/description: empty2-annotation empty test case spec: artifacts: - artifact: components/empty2/empty2.gen.yaml @@ -287,6 +319,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty4 + labels: + app.holos.run/name: empty4-label + annotations: + app.holos.run/description: empty4-annotation empty test case spec: artifacts: - artifact: components/empty4/empty4.gen.yaml @@ -312,6 +348,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty1 + labels: + app.holos.run/name: empty1-label + annotations: + app.holos.run/description: empty1-annotation empty test case spec: artifacts: - artifact: components/empty1/empty1.gen.yaml @@ -337,6 +377,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty2 + labels: + app.holos.run/name: empty2-label + annotations: + app.holos.run/description: empty2-annotation empty test case spec: artifacts: - artifact: components/empty2/empty2.gen.yaml @@ -362,6 +406,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty3 + labels: + app.holos.run/name: empty3-label + annotations: + app.holos.run/description: empty3-annotation empty test case spec: artifacts: - artifact: components/empty3/empty3.gen.yaml @@ -387,6 +435,10 @@ kind: BuildPlan apiVersion: v1alpha5 metadata: name: empty4 + labels: + app.holos.run/name: empty4-label + annotations: + app.holos.run/description: empty4-annotation empty test case spec: artifacts: - artifact: components/empty4/empty4.gen.yaml diff --git a/doc/md/api/author.md b/doc/md/api/author.md index 8028db78..57df8168 100644 --- a/doc/md/api/author.md +++ b/doc/md/api/author.md @@ -43,6 +43,11 @@ type ComponentConfig struct { // Name represents the BuildPlan metadata.name field. Used to construct the // fully rendered manifest file path. Name string + // Labels represent the BuildPlan metadata.labels field. + Labels map[string]string + // Annotations represent the BuildPlan metadata.annotations field. + Annotations map[string]string + // Path represents the path to the component producing the BuildPlan. Path string // Parameters are useful to reuse a component with various parameters. diff --git a/internal/builder/v1alpha5/builder.go b/internal/builder/v1alpha5/builder.go index 83431ddc..a053c35e 100644 --- a/internal/builder/v1alpha5/builder.go +++ b/internal/builder/v1alpha5/builder.go @@ -3,6 +3,7 @@ package v1alpha5 import ( "bytes" "context" + "encoding/json" "fmt" "log/slog" "os" @@ -60,15 +61,37 @@ func (c *Component) Describe() string { return c.Component.Name } -func (c *Component) Tags() []string { - tags := make([]string, 0, len(c.Component.Parameters)+2) +func (c *Component) Tags() ([]string, error) { + size := 2 + + len(c.Component.Parameters) + + len(c.Component.Labels) + + len(c.Component.Annotations) + + tags := make([]string, 0, size) for k, v := range c.Component.Parameters { tags = append(tags, k+"="+v) } // Inject holos component metadata tags. tags = append(tags, "holos_component_name="+c.Component.Name) tags = append(tags, "holos_component_path="+c.Component.Path) - return tags + + if len(c.Component.Labels) > 0 { + labels, err := json.Marshal(c.Component.Labels) + if err != nil { + return nil, err + } + tags = append(tags, "holos_component_labels="+string(labels)) + } + + if len(c.Component.Annotations) > 0 { + annotations, err := json.Marshal(c.Component.Annotations) + if err != nil { + return nil, err + } + tags = append(tags, "holos_component_annotations="+string(annotations)) + } + + return tags, nil } func (c *Component) WriteTo() string { diff --git a/internal/cli/render/render.go b/internal/cli/render/render.go index 359f9718..8fae5882 100644 --- a/internal/cli/render/render.go +++ b/internal/cli/render/render.go @@ -140,7 +140,10 @@ func makePlatformRenderFunc(w io.Writer, prefixArgs []string) builder.BuildFunc case <-ctx.Done(): return errors.Wrap(ctx.Err()) default: - tags := component.Tags() + tags, err := component.Tags() + if err != nil { + return errors.Wrap(err) + } args := make([]string, 0, 10+len(prefixArgs)+(len(tags)*2)) args = append(args, prefixArgs...) args = append(args, "render", "component") diff --git a/internal/cli/show.go b/internal/cli/show.go index 5f793cd9..a790da7c 100644 --- a/internal/cli/show.go +++ b/internal/cli/show.go @@ -116,7 +116,11 @@ func makeBuildFunc(encoder holos.OrderedEncoder, opts holos.BuildOpts) builder.B case <-ctx.Done(): return errors.Wrap(ctx.Err()) default: - inst, err := builder.LoadInstance(component.Path(), component.Tags()) + tags, err := component.Tags() + if err != nil { + return errors.Wrap(err) + } + inst, err := builder.LoadInstance(component.Path(), tags) if err != nil { return errors.Wrap(err) } diff --git a/internal/generate/platforms/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha5/definitions_go_gen.cue b/internal/generate/platforms/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha5/definitions_go_gen.cue index f2c080c2..5d253960 100644 --- a/internal/generate/platforms/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha5/definitions_go_gen.cue +++ b/internal/generate/platforms/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha5/definitions_go_gen.cue @@ -49,6 +49,12 @@ import "github.com/holos-run/holos/api/core/v1alpha5:core" // fully rendered manifest file path. Name: string + // Labels represent the BuildPlan metadata.labels field. + Labels: {[string]: string} @go(,map[string]string) + + // Annotations represent the BuildPlan metadata.annotations field. + Annotations: {[string]: string} @go(,map[string]string) + // Path represents the path to the component producing the BuildPlan. Path: string diff --git a/internal/generate/platforms/cue.mod/pkg/github.com/holos-run/holos/api/author/v1alpha5/definitions.cue b/internal/generate/platforms/cue.mod/pkg/github.com/holos-run/holos/api/author/v1alpha5/definitions.cue index 62cde8a5..26cf5e27 100644 --- a/internal/generate/platforms/cue.mod/pkg/github.com/holos-run/holos/api/author/v1alpha5/definitions.cue +++ b/internal/generate/platforms/cue.mod/pkg/github.com/holos-run/holos/api/author/v1alpha5/definitions.cue @@ -35,6 +35,8 @@ import ( // https://holos.run/docs/next/api/author/#Kubernetes #Kubernetes: { Name: _ + Labels: _ + Annotations: _ Path: _ Parameters: _ Resources: _ @@ -84,6 +86,12 @@ import ( BuildPlan: { metadata: name: Name + if len(Labels) != 0 { + metadata: labels: Labels + } + if len(Annotations) != 0 { + metadata: annotations: Annotations + } spec: artifacts: [for x in Artifacts {x}] } } @@ -91,6 +99,8 @@ import ( // https://holos.run/docs/next/api/author/#Helm #Helm: { Name: _ + Labels: _ + Annotations: _ Path: _ Parameters: _ Resources: _ @@ -170,6 +180,12 @@ import ( BuildPlan: { metadata: name: Name + if len(Labels) != 0 { + metadata: labels: Labels + } + if len(Annotations) != 0 { + metadata: annotations: Annotations + } spec: artifacts: [for x in Artifacts {x}] } } diff --git a/internal/generate/platforms/v1alpha5/schema.cue b/internal/generate/platforms/v1alpha5/schema.cue index 2343d8f8..f59366e9 100644 --- a/internal/generate/platforms/v1alpha5/schema.cue +++ b/internal/generate/platforms/v1alpha5/schema.cue @@ -6,6 +6,16 @@ import "github.com/holos-run/holos/api/author/v1alpha5:author" Name: _Tags.component.name Path: _Tags.component.path Resources: #Resources + + // labels is an optional field, guard references to it. + if _Tags.component.labels != _|_ { + Labels: _Tags.component.labels + } + + // annotations is an optional field, guard references to it. + if _Tags.component.annotations != _|_ { + Annotations: _Tags.component.annotations + } } // https://holos.run/docs/api/author/v1alpha5/#Kubernetes diff --git a/internal/generate/platforms/v1alpha5/tags.cue b/internal/generate/platforms/v1alpha5/tags.cue index c8a07043..7491ccf8 100644 --- a/internal/generate/platforms/v1alpha5/tags.cue +++ b/internal/generate/platforms/v1alpha5/tags.cue @@ -1,6 +1,10 @@ package holos -import "github.com/holos-run/holos/api/core/v1alpha5:core" +import ( + "encoding/json" + + "github.com/holos-run/holos/api/core/v1alpha5:core" +) // Note: tags should have a reasonable default value for cue export. _Tags: { @@ -8,5 +12,23 @@ _Tags: { component: core.#Component & { name: string | *"no-name" @tag(holos_component_name, type=string) path: string | *"no-path" @tag(holos_component_path, type=string) + + _labels_json: string | *"" @tag(holos_component_labels, type=string) + _labels: {} + if _labels_json != "" { + _labels: json.Unmarshal(_labels_json) + } + for k, v in _labels { + labels: (k): v + } + + _annotations_json: string | *"" @tag(holos_component_annotations, type=string) + _annotations: {} + if _annotations_json != "" { + _annotations: json.Unmarshal(_annotations_json) + } + for k, v in _annotations { + annotations: (k): v + } } } diff --git a/internal/holos/interface.go b/internal/holos/interface.go index 4987367e..3ecbd00d 100644 --- a/internal/holos/interface.go +++ b/internal/holos/interface.go @@ -21,7 +21,7 @@ type Platform interface { type Component interface { Describe() string Path() string - Tags() []string + Tags() ([]string, error) WriteTo() string Labels() Labels }