Compare commits

..

2 Commits

Author SHA1 Message Date
Jeff McCune
53cb9ba7fb (#189) Make the v1alpha2 API data only
Previously a couple of methods were defined on the Result struct.

This patch moves the methods to an internal wrapper struct to remove
them from the API documentation.

With this patch the API between holos and CUE is entirely a data API.
2024-06-30 17:19:35 -07:00
Jeff McCune
4cc139b372 (#189) v1alpha2 Reference Docs 2024-06-30 16:13:12 -07:00
15 changed files with 191 additions and 110 deletions

View File

@@ -2,37 +2,43 @@ package v1alpha2
import "google.golang.org/protobuf/types/known/structpb"
// Label is an arbitrary unique identifier. Defined as a type for clarity and type checking.
// Label is an arbitrary unique identifier internal to holos itself. The holos
// cli is expected to never write a Label value to rendered output files,
// therefore use a [Label] then the identifier must be unique and internal.
// Defined as a type for clarity and type checking.
//
// A Label is useful to convert a CUE struct to a list, for example producing a list of [APIObject] resources from an [APIObjectMap]. A CUE struct using
// Label keys is guaranteed to not lose data when rendering output because a
// Label is expected to never be written to the final output.
type Label string
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
type Kind string
// APIObject represents the most basic generic form of a single kubernetes api
// object. Represented as a JSON object internally for compatibility between
// tools, for example loading from CUE.
type APIObject structpb.Struct
// APIObjectMap represents the marshalled yaml representation of kubernetes api
// objects. Do not produce an APIObjectMap directly, instead use [APIObjects]
// to produce the marshalled yaml representation from CUE data, then provide the
// result to [HolosComponent].
type APIObjectMap map[Kind]map[Label]string
// APIObjects represents kubernetes api objects to apply to the api server.
// Useful to mix in resources to each HolosComponent type, for example adding an
// ExternalSecret to a HelmChart HolosComponent.
// APIObjects represents Kubernetes API objects defined directly from CUE code.
// Useful to mix in resources to any kind of [HolosComponent], for example
// adding an ExternalSecret resource to a [HelmChart].
//
// Kind must be the resource kind, e.g. Deployment or Service.
// [Kind] must be the resource kind, e.g. Deployment or Service.
//
// Label is an arbitrary internal identifier to uniquely identify the resource
// [Label] is an arbitrary internal identifier to uniquely identify the resource
// within the context of a `holos` command. Holos will never write the
// intermediate label to rendered output.
//
// Refer to [HolosComponent] which accepts an [APIObjectMap] field provided by
// [APIObjects].
type APIObjects struct {
// APIObjects represents Kubernetes API objects defined directly from CUE
// code. Useful to mix in resources, for example adding an ExternalSecret
// resource to a HelmChart HolosComponent.
APIObjects map[Kind]map[Label]structpb.Struct `json:"apiObjects"`
// APIObjectMap represents the marshalled yaml representation of APIObjects,
// useful to inspect the rendered representation of the resource which will be
// sent to the kubernetes API server.
APIObjectMap APIObjectMap `json:"apiObjectMap"`
APIObjects map[Kind]map[Label]APIObject `json:"apiObjects"`
APIObjectMap APIObjectMap `json:"apiObjectMap"`
}

View File

@@ -1,17 +1,19 @@
package v1alpha2
import (
"fmt"
"strings"
)
// FilePath represents a file path.
type FilePath string
// FileContentMap represents a mapping of file names to file content.
type FileContentMap map[string]string
// FileContent represents file contents.
type FileContent string
// BuildPlan represents a build plan for the holos cli to execute. A build plan
// is a set of zero or more holos components. The purpose of a BuildPlan is to
// define one or more [HolosComponent] kinds, for example a [HelmChart] or
// [KustomizeBuild].
// FileContentMap represents a mapping of file paths to file contents. Paths
// are relative to the `holos` output "deploy" directory, and may contain
// sub-directories.
type FileContentMap map[FilePath]FileContent
// BuildPlan represents a build plan for the holos cli to execute. The purpose
// of a BuildPlan is to define one or more [HolosComponent] kinds. For example a
// [HelmChart], [KustomizeBuild], or [KubernetesObjects].
//
// A BuildPlan usually has an additional empty [KubernetesObjects] for the
// purpose of using the [HolosComponent] DeployFiles field to deploy an ArgoCD
@@ -22,41 +24,19 @@ type BuildPlan struct {
Spec BuildPlanSpec `json:"spec"`
}
func (bp *BuildPlan) Validate() error {
errs := make([]string, 0, 2)
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
}
func (bp *BuildPlan) ResultCapacity() (count int) {
if bp == nil {
return 0
}
count = len(bp.Spec.Components.HelmChartList) +
len(bp.Spec.Components.KubernetesObjectsList) +
len(bp.Spec.Components.KustomizeBuildList) +
len(bp.Spec.Components.Resources)
return count
}
// BuildPlanSpec represents the specification of the build plan.
type BuildPlanSpec struct {
Disabled bool `json:"disabled,omitempty"`
// Disabled causes the holos cli to take no action over the [BuildPlan].
Disabled bool `json:"disabled,omitempty"`
// Components represents multiple [HolosComponent] kinds to manage.
Components BuildPlanComponents `json:"components,omitempty"`
}
type BuildPlanComponents struct {
Resources map[string]KubernetesObjects `json:"resources,omitempty"`
KubernetesObjectsList []KubernetesObjects `json:"kubernetesObjectsList,omitempty"`
HelmChartList []HelmChart `json:"helmChartList,omitempty"`
KustomizeBuildList []KustomizeBuild `json:"kustomizeBuildList,omitempty"`
Resources map[Label]KubernetesObjects `json:"resources,omitempty"`
KubernetesObjectsList []KubernetesObjects `json:"kubernetesObjectsList,omitempty"`
HelmChartList []HelmChart `json:"helmChartList,omitempty"`
KustomizeBuildList []KustomizeBuild `json:"kustomizeBuildList,omitempty"`
}
// HolosComponent defines the fields common to all holos component kinds. Every

View File

@@ -1,8 +1,3 @@
// Package v1alpha2 contains the core API contract between the holos cli and cue
// configuration code. Platform designers, operators, and software developers
// use this API to write configuration in CUE which `holos` loads. The overall
// shape of the API defines imperative actions `holos` should carry out to
// render the complete yaml that represents a Platform.
package v1alpha2
import "google.golang.org/protobuf/types/known/structpb"

24
api/core/v1alpha2/doc.go Normal file
View File

@@ -0,0 +1,24 @@
// Package v1alpha2 contains the core API contract between the holos cli and CUE
// configuration code. Platform designers, operators, and software developers
// use this API to write configuration in CUE which `holos` loads. The overall
// shape of the API defines imperative actions `holos` should carry out to
// render the complete yaml that represents a Platform.
//
// [Platform] defines the complete configuration of a platform. With the holos
// reference platform this takes the shape of one management cluster and at
// least two workload cluster. Each cluster has multiple [HolosComponent]
// resources applied to it.
//
// Each holos component path, e.g. `components/namespaces` produces exactly one
// [BuildPlan] which in turn contains a set of [HolosComponent] kinds.
//
// The primary kinds of [HolosComponent] are:
//
// 1. [HelmChart] to render config from a helm chart.
// 2. [KustomizeBuild] to render config from [Kustomize]
// 3. [KubernetesObjects] to render [APIObjects] defined directly in CUE
// configuration.
//
// Note that Holos operates as a data pipeline, so the output of a [HelmChart]
// may be provided to [Kustomize] for post-processing.
package v1alpha2

View File

@@ -2,7 +2,7 @@ package v1alpha2
const KubernetesObjectsKind = "KubernetesObjects"
// KubernetesObjects represents a [HolosComponent] composed of kubernetes api
// KubernetesObjects represents a [HolosComponent] composed of Kubernetes API
// objects provided directly from CUE using [APIObjects].
type KubernetesObjects struct {
HolosComponent `json:",inline"`

View File

@@ -1,7 +1,7 @@
package v1alpha2
// KustomizeBuild renders plain yaml files in the holos component directory
// using kubectl kustomize build.
// KustomizeBuild represents a [HolosComponent] that renders plain yaml files in
// the holos component directory using `kubectl kustomize build`.
type KustomizeBuild struct {
HolosComponent `json:",inline"`
Kind string `json:"kind" cue:"\"KustomizeBuild\""`

View File

@@ -49,6 +49,43 @@ type Builder struct {
cfg config
}
type buildPlanWrapper struct {
buildPlan *v1alpha2.BuildPlan
}
func (b *buildPlanWrapper) validate() error {
if b == nil {
return fmt.Errorf("invalid BuildPlan: is nil")
}
bp := b.buildPlan
if bp == nil {
return fmt.Errorf("invalid BuildPlan: is nil")
}
errs := make([]string, 0, 2)
if bp.Kind != v1alpha2.BuildPlanKind {
errs = append(errs, fmt.Sprintf("kind invalid: want: %s have: %s", v1alpha1.BuildPlanKind, bp.Kind))
}
if bp.APIVersion != v1alpha2.APIVersion {
errs = append(errs, fmt.Sprintf("apiVersion invalid: want: %s have: %s", v1alpha2.APIVersion, bp.APIVersion))
}
if len(errs) > 0 {
return fmt.Errorf("invalid BuildPlan: " + strings.Join(errs, ", "))
}
return nil
}
func (b *buildPlanWrapper) resultCapacity() (count int) {
if b == nil {
return 0
}
bp := b.buildPlan
count = len(bp.Spec.Components.HelmChartList) +
len(bp.Spec.Components.KubernetesObjectsList) +
len(bp.Spec.Components.KustomizeBuildList) +
len(bp.Spec.Components.Resources)
return count
}
// New returns a new *Builder configured by opts Option.
func New(opts ...Option) *Builder {
var cfg config
@@ -214,7 +251,9 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha2.BuildPlan, path holos.InstancePath) (results []*render.Result, err error) {
log := logger.FromContext(ctx)
if err := buildPlan.Validate(); err != nil {
bpw := buildPlanWrapper{buildPlan: buildPlan}
if err := bpw.validate(); err != nil {
log.WarnContext(ctx, "could not validate", "skipped", true, "err", err)
return nil, errors.Wrap(fmt.Errorf("could not validate %w", err))
}
@@ -224,8 +263,8 @@ func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha2.BuildPlan,
return
}
results = make([]*render.Result, 0, buildPlan.ResultCapacity())
log.DebugContext(ctx, "allocated results slice", "cap", buildPlan.ResultCapacity())
results = make([]*render.Result, 0, bpw.resultCapacity())
log.DebugContext(ctx, "allocated results slice", "cap", bpw.resultCapacity())
for _, component := range buildPlan.Spec.Components.Resources {
ko := render.KubernetesObjects{Component: component}

View File

@@ -6,42 +6,43 @@ package v1alpha2
import "google.golang.org/protobuf/types/known/structpb"
// Label is an arbitrary unique identifier. Defined as a type for clarity and type checking.
// Label is an arbitrary unique identifier internal to holos itself. The holos
// cli is expected to never write a Label value to rendered output files,
// therefore use a [Label] then the identifier must be unique and internal.
// Defined as a type for clarity and type checking.
//
// A Label is useful to convert a CUE struct to a list, for example producing a list of [APIObject] resources from an [APIObjectMap]. A CUE struct using
// Label keys is guaranteed to not lose data when rendering output because a
// Label is expected to never be written to the final output.
#Label: string
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
#Kind: string
// APIObject represents the most basic generic form of a single kubernetes api
// object. Represented as a JSON object internally for compatibility between
// tools, for example loading from CUE.
#APIObject: structpb.#Struct
// APIObjectMap represents the marshalled yaml representation of kubernetes api
// objects. Do not produce an APIObjectMap directly, instead use [APIObjects]
// to produce the marshalled yaml representation from CUE data.
//
// Example:
//
// # CUE
// apiObjectMap: (#APIObjects & {apiObjects: Resources}).apiObjectMap
// to produce the marshalled yaml representation from CUE data, then provide the
// result to [HolosComponent].
#APIObjectMap: {[string]: [string]: string}
// APIObjects represents kubernetes api objects to apply to the api server.
// Useful to mix in resources to each HolosComponent type, for example adding an
// ExternalSecret to a HelmChart HolosComponent.
// APIObjects represents Kubernetes API objects defined directly from CUE code.
// Useful to mix in resources to any kind of [HolosComponent], for example
// adding an ExternalSecret resource to a [HelmChart].
//
// Kind must be the resource kind, e.g. Deployment or Service.
// [Kind] must be the resource kind, e.g. Deployment or Service.
//
// Label is an arbitrary internal identifier to uniquely identify the resource
// [Label] is an arbitrary internal identifier to uniquely identify the resource
// within the context of a `holos` command. Holos will never write the
// intermediate label to rendered output.
//
// Refer to [HolosComponent] which accepts an [APIObjectMap] field provided by
// [APIObjects].
#APIObjects: {
// APIObjects represents Kubernetes API objects defined directly from CUE
// code. Useful to mix in resources, for example adding an ExternalSecret
// resource to a HelmChart HolosComponent.
apiObjects: {[string]: [string]: structpb.#Struct} @go(APIObjects,map[Kind]map[Label]structpb.Struct)
// APIObjectMap represents the marshalled yaml representation of APIObjects,
// useful to inspect the rendered representation of the resource which will be
// sent to the kubernetes API server.
apiObjects: {[string]: [string]: #APIObject} @go(APIObjects,map[Kind]map[Label]APIObject)
apiObjectMap: #APIObjectMap @go(APIObjectMap)
}

View File

@@ -4,13 +4,20 @@
package v1alpha2
// FileContentMap represents a mapping of file names to file content.
#FileContentMap: {[string]: string}
// FilePath represents a file path.
#FilePath: string
// BuildPlan represents a build plan for the holos cli to execute. A build plan
// is a set of zero or more holos components. The purpose of a BuildPlan is to
// define one or more [HolosComponent] kinds, for example a [HelmChart] or
// [KustomizeBuild].
// FileContent represents file contents.
#FileContent: string
// FileContentMap represents a mapping of file paths to file contents. Paths
// are relative to the `holos` output "deploy" directory, and may contain
// sub-directories.
#FileContentMap: {[string]: #FileContent}
// BuildPlan represents a build plan for the holos cli to execute. The purpose
// of a BuildPlan is to define one or more [HolosComponent] kinds. For example a
// [HelmChart], [KustomizeBuild], or [KubernetesObjects].
//
// A BuildPlan usually has an additional empty [KubernetesObjects] for the
// purpose of using the [HolosComponent] DeployFiles field to deploy an ArgoCD
@@ -21,13 +28,17 @@ package v1alpha2
spec: #BuildPlanSpec @go(Spec)
}
// BuildPlanSpec represents the specification of the build plan.
#BuildPlanSpec: {
disabled?: bool @go(Disabled)
// Disabled causes the holos cli to take no action over the [BuildPlan].
disabled?: bool @go(Disabled)
// Components represents multiple [HolosComponent] kinds to manage.
components?: #BuildPlanComponents @go(Components)
}
#BuildPlanComponents: {
resources?: {[string]: #KubernetesObjects} @go(Resources,map[string]KubernetesObjects)
resources?: {[string]: #KubernetesObjects} @go(Resources,map[Label]KubernetesObjects)
kubernetesObjectsList?: [...#KubernetesObjects] @go(KubernetesObjectsList,[]KubernetesObjects)
helmChartList?: [...#HelmChart] @go(HelmChartList,[]HelmChart)
kustomizeBuildList?: [...#KustomizeBuild] @go(KustomizeBuildList,[]KustomizeBuild)

View File

@@ -2,15 +2,15 @@
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
// Package v1alpha2 contains the core API contract between the holos cli and cue
// configuration code. Platform designers, operators, and software developers
// use this API to write configuration in CUE which `holos` loads. The overall
// shape of the API defines imperative actions `holos` should carry out to
// render the complete yaml that represents a Platform.
package v1alpha2
import "google.golang.org/protobuf/types/known/structpb"
#PlatformMetadata: {
// Name represents the Platform name.
name: string @go(Name)
}
// Platform represents a platform to manage. A Platform resource informs holos
// which components to build. The platform resource also acts as a container
// for the platform model form values provided by the PlatformService. The
@@ -24,10 +24,7 @@ import "google.golang.org/protobuf/types/known/structpb"
apiVersion: string & (string | *"v1alpha2") @go(APIVersion)
// Metadata represents data about the object such as the Name.
metadata: {
// Name represents the Platform name.
name: string @go(Name)
} @go(Metadata,"struct{Name string \"json:\\\"name\\\" yaml:\\\"name\\\"\"}")
metadata: #PlatformMetadata @go(Metadata)
// Spec represents the specification.
spec: #PlatformSpec @go(Spec)

View File

@@ -0,0 +1,28 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
// Package v1alpha2 contains the core API contract between the holos cli and CUE
// configuration code. Platform designers, operators, and software developers
// use this API to write configuration in CUE which `holos` loads. The overall
// shape of the API defines imperative actions `holos` should carry out to
// render the complete yaml that represents a Platform.
//
// [Platform] defines the complete configuration of a platform. With the holos
// reference platform this takes the shape of one management cluster and at
// least two workload cluster. Each cluster has multiple [HolosComponent]
// resources applied to it.
//
// Each holos component path, e.g. `components/namespaces` produces exactly one
// [BuildPlan] which in turn contains a set of [HolosComponent] kinds.
//
// The primary kinds of [HolosComponent] are:
//
// 1. [HelmChart] to render config from a helm chart.
// 2. [KustomizeBuild] to render config from [Kustomize]
// 3. [KubernetesObjects] to render [APIObjects] defined directly in CUE
// configuration.
//
// Note that Holos operates as a data pipeline, so the output of a [HelmChart]
// may be provided to [Kustomize] for post-processing.
package v1alpha2

View File

@@ -6,8 +6,8 @@ package v1alpha2
#KubernetesObjectsKind: "KubernetesObjects"
// KubernetesObjects represents a holos component composed of kubernetes api
// objects provided directly from CUE.
// KubernetesObjects represents a [HolosComponent] composed of Kubernetes API
// objects provided directly from CUE using [APIObjects].
#KubernetesObjects: {
#HolosComponent
kind: string & "KubernetesObjects" @go(Kind)

View File

@@ -4,8 +4,8 @@
package v1alpha2
// KustomizeBuild renders plain yaml files in the holos component directory
// using kubectl kustomize build.
// KustomizeBuild represents a [HolosComponent] that renders plain yaml files in
// the holos component directory using `kubectl kustomize build`.
#KustomizeBuild: {
#HolosComponent
kind: string & "KustomizeBuild" @go(Kind)

View File

@@ -157,7 +157,7 @@ func (r *Result) kustomize(ctx context.Context) error {
// Write the kustomization tree, kustomization.yaml must be in this map for kustomize to work.
for file, content := range r.Component.Kustomize.KustomizeFiles {
target := filepath.Join(tempDir, file)
target := filepath.Join(tempDir, string(file))
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return errors.Wrap(err)
}
@@ -189,8 +189,8 @@ func (r *Result) WriteDeployFiles(ctx context.Context, path string) error {
return nil
}
for k, content := range r.Component.DeployFiles {
path := filepath.Join(path, k)
if err := r.Save(ctx, path, content); err != nil {
path := filepath.Join(path, string(k))
if err := r.Save(ctx, path, string(content)); err != nil {
return errors.Wrap(err)
}
log.InfoContext(ctx, "wrote deploy file", "path", path, "bytes", len(content))

View File

@@ -1 +1 @@
0
1