Compare commits

...

2 Commits

Author SHA1 Message Date
Jeff McCune
6f0928b12c (#71) Add go BuildPlan type as the CUE<->Holos API
This patch establishes the BuildPlan struct as the single API contract
between CUE and Holos.  A BuildPlan spec contains a list of each of the
support holos component types.

The purpose of this data structure is to support the use case of one CUE
instance generating 1 build plan that contains 0..N of each type of
holos component.

The need for multiple components per one CUE instance is to support the
generation of a collection of N~4 flux kustomization resources per
project and P~6 projects built from one CUE instance.

Tested with:

    holos render --cluster-name=k2 ~/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/init/namespaces/...

Common labels are removed because they're too tightly coupled to the
model of one component per one cue instance.
2024-03-21 16:13:36 -07:00
Jeff McCune
c6e9250d60 (#69) Refactor clean up go types
Separate out the Kustomization and Kustomize types commonly used in
holos components.  Embed HolosComponent into Result.
2024-03-21 08:57:02 -07:00
25 changed files with 343 additions and 320 deletions

View File

@@ -44,13 +44,18 @@ tidy: ## Tidy go module.
go mod tidy
.PHONY: fmt
fmt: ## Format Go code.
fmt: ## Format code.
cd docs/examples && cue fmt ./...
go fmt ./...
.PHONY: vet
vet: ## Vet Go code.
go vet ./...
.PHONY: gencue
gencue: ## Generate CUE definitions
cd docs/examples && cue get go github.com/holos-run/holos/api/...
.PHONY: generate
generate: ## Generate code.
go generate ./...

39
api/v1alpha1/buildplan.go Normal file
View File

@@ -0,0 +1,39 @@
package v1alpha1
import (
"fmt"
"strings"
)
// BuildPlan is the primary interface between CUE and the Holos cli.
type BuildPlan struct {
TypeMeta `json:",inline" yaml:",inline"`
// Metadata represents the holos component name
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
Spec BuildPlanSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
}
type BuildPlanSpec struct {
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
Components BuildPlanComponents `json:"components,omitempty" yaml:"components,omitempty"`
}
type BuildPlanComponents struct {
HelmCharts []HelmChart `json:"helmCharts,omitempty" yaml:"helmCharts,omitempty"`
KubernetesObjects []KubernetesObjects `json:"kubernetesObjects,omitempty" yaml:"kubernetesObjects,omitempty"`
KustomizeBuilds []KustomizeBuild `json:"kustomizeBuilds,omitempty" yaml:"kustomizeBuilds,omitempty"`
}
func (bp *BuildPlan) Validate() error {
errs := make([]string, 0, 10)
if bp.Kind != BuildPlanKind {
errs = append(errs, fmt.Sprintf("kind invalid: want: %s have: %s", BuildPlanKind, bp.Kind))
}
if bp.APIVersion != APIVersion {
errs = append(errs, fmt.Sprintf("apiVersion invalid: want: %s have: %s", APIVersion, bp.APIVersion))
}
if len(errs) > 0 {
return fmt.Errorf("invalid BuildPlan: " + strings.Join(errs, ", "))
}
return nil
}

View File

@@ -1,12 +1,22 @@
package v1alpha1
// HolosComponent defines the common fields for all holos components.
// HolosComponent defines the fields common to all holos component kinds including the Render Result.
type HolosComponent struct {
TypeMeta `json:",inline" yaml:",inline"`
// Metadata represents the holos component name
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
// APIObjectMap holds the marshalled representation of api objects.
// APIObjectMap holds the marshalled representation of api objects. Think of
// these as resources overlaid at the back of the render pipeline.
APIObjectMap APIObjectMap `json:"apiObjectMap,omitempty" yaml:"apiObjectMap,omitempty"`
// Kustomization holds the marshalled representation of the flux kustomization.
// Kustomization holds the marshalled representation of the flux kustomization
// which reconciles resources in git with the api server.
Kustomization `json:",inline" yaml:",inline"`
// Kustomize represents a kubectl kustomize build post-processing step.
Kustomize `json:",inline" yaml:",inline"`
// Skip causes holos to take no action regarding the component.
Skip bool
}
func (hc *HolosComponent) NewResult() *Result {
return &Result{HolosComponent: *hc}
}

View File

@@ -0,0 +1,9 @@
package v1alpha1
const (
APIVersion = "holos.run/v1alpha1"
BuildPlanKind = "BuildPlan"
HelmChartKind = "HelmChart"
// ChartDir is the directory name created in the holos component directory to cache a chart.
ChartDir = "vendor"
)

View File

@@ -12,12 +12,6 @@ import (
"strings"
)
const (
HelmChartKind = "HelmChart"
// ChartDir is the directory name created in the holos component directory to cache a chart.
ChartDir = "vendor"
)
// A HelmChart represents a helm command to provide chart values in order to render kubernetes api objects.
type HelmChart struct {
HolosComponent `json:",inline" yaml:",inline"`
@@ -41,11 +35,7 @@ type Repository struct {
}
func (hc *HelmChart) Render(ctx context.Context, path holos.PathComponent) (*Result, error) {
result := Result{
TypeMeta: hc.TypeMeta,
Metadata: hc.Metadata,
Kustomization: hc.Kustomization,
}
result := Result{HolosComponent: hc.HolosComponent}
if err := hc.helm(ctx, &result, path); err != nil {
return nil, err
}

View File

@@ -4,8 +4,4 @@ package v1alpha1
type Kustomization struct {
// KsContent is the yaml representation of the flux kustomization for gitops.
KsContent string `json:"ksContent,omitempty" yaml:"ksContent,omitempty"`
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
KustomizeFiles FileContentMap `json:"kustomizeFiles,omitempty" yaml:"kustomizeFiles,omitempty"`
// ResourcesFile is the file name used for api objects in kustomization.yaml
ResourcesFile string `json:"resourcesFile,omitempty" yaml:"resourcesFile,omitempty"`
}

46
api/v1alpha1/kustomize.go Normal file
View File

@@ -0,0 +1,46 @@
package v1alpha1
import (
"context"
"github.com/holos-run/holos"
"github.com/holos-run/holos/pkg/logger"
"github.com/holos-run/holos/pkg/util"
"github.com/holos-run/holos/pkg/wrapper"
)
const KustomizeBuildKind = "KustomizeBuild"
// Kustomize represents resources necessary to execute a kustomize build.
// Intended for at least two use cases:
//
// 1. Process raw yaml file resources in a holos component directory.
// 2. Post process a HelmChart to inject istio, add custom labels, etc...
type Kustomize struct {
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
KustomizeFiles FileContentMap `json:"kustomizeFiles,omitempty" yaml:"kustomizeFiles,omitempty"`
// ResourcesFile is the file name used for api objects in kustomization.yaml
ResourcesFile string `json:"resourcesFile,omitempty" yaml:"resourcesFile,omitempty"`
}
// KustomizeBuild renders plain yaml files in the holos component directory using kubectl kustomize build.
type KustomizeBuild struct {
HolosComponent `json:",inline" yaml:",inline"`
}
// Render produces a Result by executing kubectl kustomize on the holos
// component path. Useful for processing raw yaml files.
func (kb *KustomizeBuild) Render(ctx context.Context, path holos.PathComponent) (*Result, error) {
log := logger.FromContext(ctx)
result := Result{HolosComponent: kb.HolosComponent}
// Run kustomize.
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", string(path))
if err != nil {
log.ErrorContext(ctx, kOut.Stderr.String())
return nil, wrapper.Wrap(err)
}
// Replace the accumulated output
result.accumulatedOutput = kOut.Stdout.String()
// Add CUE based api objects.
result.addObjectMap(ctx, kb.APIObjectMap)
return &result, nil
}

View File

@@ -1,37 +0,0 @@
package v1alpha1
import (
"context"
"github.com/holos-run/holos"
"github.com/holos-run/holos/pkg/logger"
"github.com/holos-run/holos/pkg/util"
"github.com/holos-run/holos/pkg/wrapper"
)
const KustomizeBuildKind = "KustomizeBuild"
// KustomizeBuild
type KustomizeBuild struct {
HolosComponent `json:",inline" yaml:",inline"`
}
// Render produces kubernetes api objects from the APIObjectMap
func (kb *KustomizeBuild) Render(ctx context.Context, path holos.PathComponent) (*Result, error) {
log := logger.FromContext(ctx)
result := Result{
TypeMeta: kb.TypeMeta,
Metadata: kb.Metadata,
Kustomization: kb.Kustomization,
}
// Run kustomize.
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", string(path))
if err != nil {
log.ErrorContext(ctx, kOut.Stderr.String())
return nil, wrapper.Wrap(err)
}
// Replace the accumulated output
result.accumulatedOutput = kOut.Stdout.String()
// Add CUE based api objects.
result.addObjectMap(ctx, kb.APIObjectMap)
return &result, nil
}

View File

@@ -13,11 +13,11 @@ import (
// Result is the build result for display or writing. Holos components Render the Result as a data pipeline.
type Result struct {
HolosComponent
TypeMeta `json:",inline" yaml:",inline"`
Kustomization `json:",inline" yaml:",inline"`
Kustomize `json:",inline" yaml:",inline"`
Metadata ObjectMeta `json:"metadata,omitempty"`
// Skip causes holos to take no action if true.
Skip bool
// accumulatedOutput accumulates rendered api objects.
accumulatedOutput string
}

View File

@@ -0,0 +1,25 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
// BuildPlan is the primary interface between CUE and the Holos cli.
#BuildPlan: {
#TypeMeta
// Metadata represents the holos component name
metadata?: #ObjectMeta @go(Metadata)
spec?: #BuildPlanSpec @go(Spec)
}
#BuildPlanSpec: {
disabled?: bool @go(Disabled)
components?: #BuildPlanComponents @go(Components)
}
#BuildPlanComponents: {
helmCharts?: [...#HelmChart] @go(HelmCharts,[]HelmChart)
kubernetesObjects?: [...#KubernetesObjects] @go(KubernetesObjects,[]KubernetesObjects)
kustomizeBuilds?: [...#KustomizeBuild] @go(KustomizeBuilds,[]KustomizeBuild)
}

View File

@@ -4,15 +4,21 @@
package v1alpha1
// HolosComponent defines the common fields for all holos components.
// HolosComponent defines the fields common to all holos component kinds including the Render Result.
#HolosComponent: {
#TypeMeta
// Metadata represents the holos component name
metadata?: #ObjectMeta @go(Metadata)
// APIObjectMap holds the marshalled representation of api objects.
// APIObjectMap holds the marshalled representation of api objects. Think of
// these as resources overlaid at the back of the render pipeline.
apiObjectMap?: #APIObjectMap @go(APIObjectMap)
#Kustomization
#Kustomize
// Skip causes holos to take no action regarding the component.
Skip: bool
}

View File

@@ -0,0 +1,12 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
#APIVersion: "holos.run/v1alpha1"
#BuildPlanKind: "BuildPlan"
#HelmChartKind: "HelmChart"
// ChartDir is the directory name created in the holos component directory to cache a chart.
#ChartDir: "vendor"

View File

@@ -4,11 +4,6 @@
package v1alpha1
#HelmChartKind: "HelmChart"
// ChartDir is the directory name created in the holos component directory to cache a chart.
#ChartDir: "vendor"
// A HelmChart represents a helm command to provide chart values in order to render kubernetes api objects.
#HelmChart: {
#HolosComponent

View File

@@ -8,10 +8,4 @@ package v1alpha1
#Kustomization: {
// KsContent is the yaml representation of the flux kustomization for gitops.
ksContent?: string @go(KsContent)
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
kustomizeFiles?: #FileContentMap @go(KustomizeFiles)
// ResourcesFile is the file name used for api objects in kustomization.yaml
resourcesFile?: string @go(ResourcesFile)
}

View File

@@ -0,0 +1,25 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
#KustomizeBuildKind: "KustomizeBuild"
// Kustomize represents resources necessary to execute a kustomize build.
// Intended for at least two use cases:
//
// 1. Process raw yaml file resources in a holos component directory.
// 2. Post process a HelmChart to inject istio, add custom labels, etc...
#Kustomize: {
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
kustomizeFiles?: #FileContentMap @go(KustomizeFiles)
// ResourcesFile is the file name used for api objects in kustomization.yaml
resourcesFile?: string @go(ResourcesFile)
}
// KustomizeBuild renders plain yaml files in the holos component directory using kubectl kustomize build.
#KustomizeBuild: {
#HolosComponent
}

View File

@@ -6,11 +6,12 @@ package v1alpha1
// Result is the build result for display or writing. Holos components Render the Result as a data pipeline.
#Result: {
HolosComponent: #HolosComponent
#TypeMeta
#Kustomization
metadata?: #ObjectMeta @go(Metadata)
// Skip causes holos to take no action if true.
Skip: bool
#Kustomize
metadata?: #ObjectMeta @go(Metadata)
}

View File

@@ -0,0 +1,8 @@
package v1alpha1
// #BuildPlan is the API contract between CUE and the Holos cli.
// Holos requires CUE to evaluate and provide a valid #BuildPlan.
#BuildPlan: {
kind: #BuildPlanKind
apiVersion: #APIVersion
}

View File

@@ -0,0 +1,3 @@
package v1alpha1
#HolosComponent: metadata: name: string

View File

@@ -0,0 +1,3 @@
package v1alpha1
#HolosComponent: Skip: true | *false

31
docs/examples/helpers.cue Normal file
View File

@@ -0,0 +1,31 @@
package holos
import "encoding/yaml"
// #APIObjects is the output type for api objects produced by cue.
#APIObjects: {
// apiObjects holds each the api objects produced by cue.
apiObjects: {
[Kind=_]: {
[Name=_]: {
kind: Kind
...
}
}
ExternalSecret?: [Name=_]: #ExternalSecret & {_name: Name}
VirtualService?: [Name=_]: #VirtualService & {metadata: name: Name}
Issuer?: [Name=_]: #Issuer & {metadata: name: Name}
}
// apiObjectMap holds the marshalled representation of apiObjects
apiObjectMap: {
for kind, v in apiObjects {
"\(kind)": {
for name, obj in v {
"\(name)": yaml.Marshal(obj)
}
}
}
...
}
}

63
docs/examples/holos.cue Normal file
View File

@@ -0,0 +1,63 @@
package holos
import (
"encoding/yaml"
h "github.com/holos-run/holos/api/v1alpha1"
ksv1 "kustomize.toolkit.fluxcd.io/kustomization/v1"
)
// The overall structure of the data is:
// 1 CUE Instance => 1 BuildPlan => 0..N HolosComponents
// Holos requires CUE to evaluate and provide a valid BuildPlan.
// Constrain each CUE instance to output a BuildPlan.
{} & h.#BuildPlan
// #HolosComponent defines struct fields common to all holos component types.
#HolosComponent: {
h.#HolosComponent
metadata: name: string
#namelen: len(metadata.name) & >=1
let Name = metadata.name
ksContent: yaml.Marshal(#Kustomization & {
metadata: name: Name
})
...
}
// Holos component types.
#HelmChart: #HolosComponent & h.#HelmChart
#KubernetesObjects: #HolosComponent & h.#KubernetesObjects
#KustomizeBuild: #HolosComponent & h.#KustomizeBuild
// #ClusterName is the cluster name for cluster scoped resources.
#ClusterName: #InputKeys.cluster
// Flux Kustomization CRDs
#Kustomization: #NamespaceObject & ksv1.#Kustomization & {
_dependsOn: [Name=_]: name: string & Name
metadata: {
name: string
namespace: string | *"flux-system"
}
spec: ksv1.#KustomizationSpec & {
interval: string | *"30m0s"
path: string | *"deploy/clusters/\(#InputKeys.cluster)/components/\(metadata.name)"
prune: bool | *true
retryInterval: string | *"2m0s"
sourceRef: {
kind: string | *"GitRepository"
name: string | *"flux-system"
}
suspend?: bool
targetNamespace?: string
timeout: string | *"3m0s"
// wait performs health checks for all reconciled resources. If set to true, .spec.healthChecks is ignored.
// Setting this to true for all components generates considerable load on the api server from watches.
// Operations are additionally more complicated when all resources are watched. Consider setting wait true for
// relatively simple components, otherwise target specific resources with spec.healthChecks.
wait: true | *false
dependsOn: [for k, v in _dependsOn {v}, ...]
}
}

View File

@@ -2,25 +2,23 @@ package holos
import "list"
#TargetNamespace: "default"
spec: components: KubernetesObjects: [
#KubernetesObjects & {
metadata: name: "prod-secrets-namespaces"
apiObjectMap: (#APIObjects & {
apiObjects: {
// #ManagedNamespaces is the set of all namespaces across all clusters in the platform.
for k, ns in #ManagedNamespaces {
if list.Contains(ns.clusterNames, #ClusterName) {
Namespace: "\(k)": #Namespace & ns.namespace
}
}
#InputKeys: {
project: "secrets"
component: "namespaces"
}
#KubernetesObjects & {
apiObjects: {
// #ManagedNamespaces is the set of all namespaces across all clusters in the platform.
for k, ns in #ManagedNamespaces {
if list.Contains(ns.clusterNames, #ClusterName) {
Namespace: "\(k)": #Namespace & ns.namespace
// #PlatformNamespaces is deprecated in favor of #ManagedNamespaces.
for ns in #PlatformNamespaces {
Namespace: "\(ns.name)": #Namespace & {metadata: ns}
}
}
}
// #PlatformNamespaces is deprecated in favor of #ManagedNamespaces.
for ns in #PlatformNamespaces {
Namespace: "\(ns.name)": #Namespace & {metadata: ns}
}
}
}
}).apiObjectMap
},
]

View File

@@ -2,7 +2,6 @@ package holos
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ksv1 "kustomize.toolkit.fluxcd.io/kustomization/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -24,49 +23,20 @@ let ResourcesFile = "resources.yaml"
// _apiVersion is the version of this schema. Defines the interface between CUE output and the holos cli.
_apiVersion: "holos.run/v1alpha1"
// #ClusterName is the cluster name for cluster scoped resources.
#ClusterName: #InputKeys.cluster
// #ComponentName is the name of the holos component.
// TODO: Refactor to support multiple components per BuildPlan
#ComponentName: #InputKeys.component
// #StageName is prod, dev, stage, etc... Usually prod for platform components.
#StageName: #InputKeys.stage
// #CollectionName is the preferred handle to the collection element of the instance name. A collection name mapes to an "application name" as described in the kubernetes recommended labels documentation. Refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/
#CollectionName: #InputKeys.project
// #ComponentName is the name of the holos component.
#ComponentName: #InputKeys.component
// #InstanceName is the name of the holos component instance being managed varying by stage, project, and component names.
#InstanceName: "\(#StageName)-\(#CollectionName)-\(#ComponentName)"
// #InstancePrefix is the stage and project without the component name. Useful for dependency management among multiple components for a project stage.
#InstancePrefix: "\(#StageName)-\(#CollectionName)"
// #TargetNamespace is the target namespace for a holos component.
#TargetNamespace: string
// #SelectorLabels are mixed into selectors.
#SelectorLabels: {
"holos.run/stage.name": #StageName
"holos.run/project.name": #CollectionName
"holos.run/component.name": #ComponentName
...
}
// #CommonLabels are mixed into every kubernetes api object.
#CommonLabels: {
#SelectorLabels
"app.kubernetes.io/part-of": #StageName
"app.kubernetes.io/name": #CollectionName
"app.kubernetes.io/component": #ComponentName
"app.kubernetes.io/instance": #InstanceName
...
}
#ClusterObject: {
_description: string | *""
metadata: metav1.#ObjectMeta & {
labels: #CommonLabels
// labels: #CommonLabels
annotations: #Description & {
_Description: _description
...
@@ -88,12 +58,8 @@ _apiVersion: "holos.run/v1alpha1"
}
// Kubernetes API Objects
#Namespace: corev1.#Namespace & #ClusterObject & {
metadata: {
name: string
labels: "kubernetes.io/metadata.name": name
}
}
#Namespace: corev1.#Namespace
#ClusterRole: #ClusterObject & rbacv1.#ClusterRole
#ClusterRoleBinding: #ClusterObject & rbacv1.#ClusterRoleBinding
#ClusterIssuer: #ClusterObject & ci.#ClusterIssuer & {...}
@@ -134,41 +100,6 @@ _apiVersion: "holos.run/v1alpha1"
}
}
// Flux Kustomization CRDs
#Kustomization: #NamespaceObject & ksv1.#Kustomization & {
metadata: {
name: #InstanceName
namespace: string | *"flux-system"
}
spec: ksv1.#KustomizationSpec & {
interval: string | *"30m0s"
path: string | *"deploy/clusters/\(#InputKeys.cluster)/components/\(#InstanceName)"
prune: bool | *true
retryInterval: string | *"2m0s"
sourceRef: {
kind: string | *"GitRepository"
name: string | *"flux-system"
}
suspend?: bool
targetNamespace?: string
timeout: string | *"3m0s"
// wait performs health checks for all reconciled resources. If set to true, .spec.healthChecks is ignored.
// Setting this to true for all components generates considerable load on the api server from watches.
// Operations are additionally more complicated when all resources are watched. Consider setting wait true for
// relatively simple components, otherwise target specific resources with spec.healthChecks.
wait: true | *false
dependsOn: [for k, v in #DependsOn {v}]
}
}
// #DependsOn stores all of the dependencies between components. It's a struct to support merging across levels in the tree.
#DependsOn: {
[Name=_]: {
name: string | *"\(#InstancePrefix)-\(Name)"
}
...
}
// External Secrets CRDs
#ExternalSecret: #NamespaceObject & es.#ExternalSecret & {
_name: string
@@ -318,65 +249,6 @@ _apiVersion: "holos.run/v1alpha1"
}
}
// #APIObjects is the output type for api objects produced by cue. A map is used to aid debugging and clarity.
#APIObjects: {
// apiObjects holds each the api objects produced by cue.
apiObjects: {
[Kind=_]: {
[Name=_]: metav1.#TypeMeta & {
kind: Kind
}
}
ExternalSecret?: [Name=_]: #ExternalSecret & {_name: Name}
VirtualService?: [Name=_]: #VirtualService & {metadata: name: Name}
Issuer?: [Name=_]: #Issuer & {metadata: name: Name}
}
// apiObjectMap holds the marshalled representation of apiObjects
apiObjectMap: {
for kind, v in apiObjects {
"\(kind)": {
for name, obj in v {
"\(name)": yaml.Marshal(obj)
}
}
}
...
}
}
// #OutputTypeMeta is shared among all output types
#OutputTypeMeta: {
// apiVersion is the output api version
apiVersion: _apiVersion
// kind is a discriminator of the type of output
kind: #PlatformSpec.kind | #KubernetesObjects.kind | #HelmChart.kind | #NoOutput.kind
// name holds a unique name suitable for a filename
metadata: name: string
// debug returns arbitrary debug output.
debug?: _
}
#NoOutput: {
#OutputTypeMeta
kind: string | *"Skip"
metadata: name: string | *"skipped"
}
// #KubernetesObjects is the output schema of a single component.
#KubernetesObjects: {
#OutputTypeMeta
#APIObjects
kind: "KubernetesObjects"
metadata: name: #InstanceName
// ksObjects holds the flux Kustomization objects for gitops
ksObjects: [...#Kustomization] | *[#Kustomization]
// ksContent is the yaml representation of kustomization
ksContent: yaml.Marshal(#Kustomization)
// platform returns the platform data structure for visibility / troubleshooting.
platform: #Platform
}
// #Chart defines an upstream helm chart
#Chart: {
name: string
@@ -391,57 +263,6 @@ _apiVersion: "holos.run/v1alpha1"
// #ChartValues represent the values provided to a helm chart. Existing values may be imorted using cue import values.yaml -p holos then wrapping the values.cue content in #Values: {}
#ChartValues: {...}
// #HelmChart is a holos component which produces kubernetes api objects from cue values provided to the helm template command.
#HelmChart: {
#OutputTypeMeta
#APIObjects
kind: "HelmChart"
metadata: name: #InstanceName
// ksObjects holds the flux Kustomization objects for gitops.
ksObjects: [...#Kustomization] | *[#Kustomization]
// ksContent is the yaml representation of kustomization.
ksContent: yaml.MarshalStream(ksObjects)
// namespace defines the value passed to the helm --namespace flag
namespace: #TargetNamespace
// chart defines the upstream helm chart to process.
chart: #Chart
// values represents the helm values to provide to the chart.
values: #ChartValues
// valuesContent holds the values yaml
valuesContent: yaml.Marshal(values)
// platform returns the platform data structure for visibility / troubleshooting.
platform: #Platform
// instance returns the key values of the holos component instance.
instance: #InputKeys
// resources is the intermediate file name for api objects.
resourcesFile: ResourcesFile
// kustomizeFiles represents the files in a kustomize directory tree.
kustomizeFiles: #KustomizeFiles.Files
// enableHooks removes the --no-hooks flag from helm template
enableHooks: true | *false
}
// #KustomizeBuild is a holos component that uses plain yaml files as the source of api objects for a holos component.
// Intended for upstream components like the CrunchyData Postgres Operator. The holos cli is expected to execute kustomize build on the component directory to produce the rendered output.
#KustomizeBuild: {
#OutputTypeMeta
#APIObjects
kind: "KustomizeBuild"
metadata: name: #InstanceName
// ksObjects holds the flux Kustomization objects for gitops.
ksObjects: [...#Kustomization] | *[#Kustomization]
// ksContent is the yaml representation of kustomization.
ksContent: yaml.MarshalStream(ksObjects)
// namespace defines the value passed to the helm --namespace flag
namespace: #TargetNamespace
}
// #PlatformSpec is the output schema of a platform specification.
#PlatformSpec: {
#OutputTypeMeta
kind: "PlatformSpec"
}
// #SecretName is the name of a Secret, ususally coupling a Deployment to an ExternalSecret
#SecretName: string
@@ -507,8 +328,3 @@ _apiVersion: "holos.run/v1alpha1"
// #IsPrimaryCluster is true if the cluster being rendered is the primary cluster
// Used by the iam project to determine where https://login.example.com is active.
#IsPrimaryCluster: bool & #ClusterName == #Platform.primaryCluster.name
// By default, render kind: Skipped so holos knows to skip over intermediate cue files.
// This enables the use of holos render ./foo/bar/baz/... when bar contains intermediary constraints which are not complete components.
// Holos skips over these intermediary cue instances.
{} & #NoOutput

View File

@@ -111,9 +111,9 @@ func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err erro
return results, err
}
// Each CUE instance provides a BuildPlan
for _, instance := range instances {
var info v1alpha1.TypeMeta
var component v1alpha1.Renderer
var buildPlan v1alpha1.BuildPlan
log := logger.FromContext(ctx).With("dir", instance.Dir)
if err := instance.Err; err != nil {
@@ -128,44 +128,29 @@ func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err erro
if err := value.Validate(); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not validate: %w", err))
}
log.DebugContext(ctx, "cue: decoding holos component build info")
if err := value.Decode(&info); err != nil {
log.DebugContext(ctx, "cue: decoding holos build plan")
if err := value.Decode(&buildPlan); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
}
log.DebugContext(ctx, "cue: processing holos component kind "+info.Kind)
switch info.Kind {
case Skip:
results = append(results, &v1alpha1.Result{Skip: true})
continue
case KubernetesObjects:
var ko v1alpha1.KubernetesObjects
if err := value.Decode(&ko); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
}
component = &ko
case Helm:
var hc v1alpha1.HelmChart
if err := value.Decode(&hc); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
}
component = &hc
case KustomizeBuild:
var kb v1alpha1.KustomizeBuild
if err := value.Decode(&kb); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
}
component = &kb
default:
return nil, wrapper.Wrap(fmt.Errorf("build kind not implemented: %v", info.Kind))
}
// Render the holos component into kubernetes api objects.
result, err := v1alpha1.Render(ctx, component, holos.PathComponent(instance.Dir))
if err != nil {
if err := buildPlan.Validate(); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
}
results = append(results, result)
if buildPlan.Spec.Disabled {
log.DebugContext(ctx, "skipped: spec.disabled is true", "skipped", true)
continue
}
for _, ko := range buildPlan.Spec.Components.KubernetesObjects {
result, err := ko.Render(ctx, holos.PathComponent(instance.Dir))
if err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
}
results = append(results, result)
}
// TODO: HelmCharts
// TODO: KustomizeBuilds
}
return results, nil

View File

@@ -1 +1 @@
59
60