mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 16:54:58 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2d5c4ad36 | ||
|
|
ab03ef1052 | ||
|
|
8c76061b0d |
42
cmd/holos/testdata/constraints.txt
vendored
Normal file
42
cmd/holos/testdata/constraints.txt
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# Want support for intermediary constraints
|
||||
exec holos build ./foo/... --log-level debug
|
||||
stdout '^bf2bc7f9-9ba0-4f9e-9bd2-9a205627eb0b$'
|
||||
stderr 'processing holos component kind Skip'
|
||||
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- foo/constraints.cue --
|
||||
package holos
|
||||
|
||||
metadata: name: "jeff"
|
||||
-- foo/bar/bar.cue --
|
||||
package holos
|
||||
|
||||
#KubernetesObjects & {
|
||||
apiObjectMap: foo: bar: "bf2bc7f9-9ba0-4f9e-9bd2-9a205627eb0b"
|
||||
}
|
||||
-- schema.cue --
|
||||
package holos
|
||||
|
||||
cluster: string @tag(cluster, string)
|
||||
|
||||
// #OutputTypeMeta is shared among all output types
|
||||
#OutputTypeMeta: {
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: #KubernetesObjects.kind | #NoOutput.kind
|
||||
metadata: name: string
|
||||
}
|
||||
|
||||
#KubernetesObjects: {
|
||||
#OutputTypeMeta
|
||||
kind: "KubernetesObjects"
|
||||
apiObjectMap: {...}
|
||||
}
|
||||
|
||||
#NoOutput: {
|
||||
#OutputTypeMeta
|
||||
kind: string | *"Skip"
|
||||
metadata: name: string | *"skipped"
|
||||
}
|
||||
|
||||
#NoOutput & {}
|
||||
@@ -1,7 +1,7 @@
|
||||
# Want kube api objects in the apiObjects output.
|
||||
exec holos build .
|
||||
stdout '^kind: SecretStore$'
|
||||
stdout '# Source: holos component cue api objects SecretStore/default'
|
||||
stdout '# Source: CUE apiObjects.SecretStore.default'
|
||||
|
||||
-- cue.mod --
|
||||
package holos
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Want kube api objects in the apiObjects output.
|
||||
exec holos build .
|
||||
stdout '^kind: SecretStore$'
|
||||
stdout '# Source: holos component cue api objects SecretStore/default'
|
||||
stdout '# Source: CUE apiObjects.SecretStore.default'
|
||||
stderr 'skipping helm: no chart name specified'
|
||||
|
||||
-- cue.mod --
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package holos
|
||||
|
||||
// https://cert-manager.io/docs/
|
||||
|
||||
#TargetNamespace: "cert-manager"
|
||||
|
||||
#InputKeys: {
|
||||
project: "mesh"
|
||||
component: "certmanager"
|
||||
service: "cert-manager"
|
||||
}
|
||||
|
||||
#HelmChart & {
|
||||
values: installCrds: true
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "cert-manager"
|
||||
version: "1.14.3"
|
||||
repository: {
|
||||
name: "jetstack"
|
||||
url: "https://charts.jetstack.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package holos
|
||||
|
||||
// All components are share this collection
|
||||
#InputKeys: project: "mesh"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#Kustomization: spec: {
|
||||
dependsOn: [{name: "\(#StageName)-secrets-namespaces"}, ...]
|
||||
targetNamespace: #TargetNamespace
|
||||
}
|
||||
@@ -11,31 +11,34 @@ import (
|
||||
"encoding/yaml"
|
||||
)
|
||||
|
||||
// #ClusterName is the cluster name for cluster scoped resources.
|
||||
#ClusterName: #InputKeys.cluster
|
||||
|
||||
// _apiVersion is the version of this schema. Defines the interface between CUE output and the holos cli.
|
||||
_apiVersion: "holos.run/v1alpha1"
|
||||
|
||||
// #Name defines the name: string key value pair used all over the place.
|
||||
#Name: name: string
|
||||
// #ClusterName is the cluster name for cluster scoped resources.
|
||||
#ClusterName: #InputKeys.cluster
|
||||
// #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
|
||||
|
||||
// #InstanceName is the name of the holos component instance being managed varying by stage, project, and component names.
|
||||
#InstanceName: "\(#InputKeys.stage)-\(#InputKeys.project)-\(#InputKeys.component)"
|
||||
|
||||
// #InstancePrefix is the stage and project without the component name. Useful for dependency management among multiple components for a project stage.
|
||||
#InstancePrefix: "\(#InputKeys.stage)-\(#InputKeys.project)"
|
||||
|
||||
// TypeMeta indicates a kubernetes api object
|
||||
#TypeMeta: metav1.#TypeMeta
|
||||
|
||||
// #CommonLabels are mixed into every kubernetes api object.
|
||||
#CommonLabels: {
|
||||
"holos.run/stage.name": #InputKeys.stage
|
||||
"holos.run/project.name": #InputKeys.project
|
||||
"holos.run/component.name": #InputKeys.component
|
||||
"holos.run/stage.name": #StageName
|
||||
"holos.run/project.name": #CollectionName
|
||||
"holos.run/component.name": #ComponentName
|
||||
"app.kubernetes.io/part-of": #StageName
|
||||
"app.kubernetes.io/name": #CollectionName
|
||||
"app.kubernetes.io/component": #ComponentName
|
||||
"app.kubernetes.io/instance": #InstanceName
|
||||
...
|
||||
}
|
||||
|
||||
@@ -138,8 +141,6 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
cluster: string @tag(cluster, type=string)
|
||||
// stage is usually set by the platform or project.
|
||||
stage: *"prod" | string @tag(stage, type=string)
|
||||
// project is usually set by the platform or project.
|
||||
project: string @tag(project, type=string)
|
||||
// service is usually set by the component.
|
||||
service: string @tag(service, type=string)
|
||||
// component is the name of the component
|
||||
@@ -168,7 +169,7 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
}
|
||||
stages: [ID=_]: {
|
||||
name: string & ID
|
||||
environments: [...#Name]
|
||||
environments: [...{name: string}]
|
||||
}
|
||||
projects: [ID=_]: {
|
||||
name: string & ID
|
||||
@@ -206,24 +207,25 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
// apiVersion is the output api version
|
||||
apiVersion: _apiVersion
|
||||
// kind is a discriminator of the type of output
|
||||
kind: #PlatformSpec.kind | #KubernetesObjects.kind | #HelmChart.kind
|
||||
kind: #PlatformSpec.kind | #KubernetesObjects.kind | #HelmChart.kind | #NoOutput.kind
|
||||
// name holds a unique name suitable for a filename
|
||||
metadata: name: string
|
||||
// contentType is the standard MIME type indicating the content type of the content field
|
||||
contentType: *"application/yaml" | "application/json"
|
||||
// content holds the content text output
|
||||
content: string | *""
|
||||
// debug returns arbitrary debug output.
|
||||
debug?: _
|
||||
}
|
||||
|
||||
#NoOutput: {
|
||||
#OutputTypeMeta
|
||||
kind: string | *"Skip"
|
||||
metadata: name: string | *"skipped"
|
||||
}
|
||||
|
||||
// #KubernetesObjectOutput is the output schema of a single component.
|
||||
#KubernetesObjects: {
|
||||
#OutputTypeMeta
|
||||
#APIObjects
|
||||
|
||||
// kind KubernetesObjects provides a yaml text stream of kubernetes api objects in the out field.
|
||||
kind: "KubernetesObjects"
|
||||
metadata: name: #InstanceName
|
||||
// ksObjects holds the flux Kustomization objects for gitops
|
||||
ksObjects: [...#Kustomization] | *[#Kustomization]
|
||||
// ksContent is the yaml representation of kustomization
|
||||
@@ -232,8 +234,6 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
platform: #Platform
|
||||
}
|
||||
|
||||
objects: "not allowed"
|
||||
|
||||
// #Chart defines an upstream helm chart
|
||||
#Chart: {
|
||||
name: string
|
||||
@@ -251,8 +251,8 @@ objects: "not allowed"
|
||||
#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.
|
||||
@@ -277,10 +277,10 @@ objects: "not allowed"
|
||||
kind: "PlatformSpec"
|
||||
}
|
||||
|
||||
#Output: #PlatformSpec | #KubernetesObjects | #HelmChart
|
||||
|
||||
// Holos component name
|
||||
metadata: name: #InstanceName
|
||||
|
||||
// #SecretName is the name of a Secret, ususally coupling a Deployment to an ExternalSecret
|
||||
#SecretName: string
|
||||
|
||||
// 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
|
||||
|
||||
@@ -20,6 +20,9 @@ func makeBuildRunFunc(cfg *holos.Config) command.RunFunc {
|
||||
}
|
||||
outs := make([]string, 0, len(results))
|
||||
for _, result := range results {
|
||||
if result.Skip {
|
||||
continue
|
||||
}
|
||||
outs = append(outs, result.FinalOutput())
|
||||
}
|
||||
out := strings.Join(outs, "---\n")
|
||||
|
||||
@@ -27,6 +27,9 @@ func makeRenderRunFunc(cfg *holos.Config) command.RunFunc {
|
||||
// the same file path. Write files into a blank temporary directory, error if a
|
||||
// file exists, then move the directory into place.
|
||||
for _, result := range results {
|
||||
if result.Skip {
|
||||
continue
|
||||
}
|
||||
// API Objects
|
||||
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
|
||||
if err := result.Save(ctx, path, result.FinalOutput()); err != nil {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"cuelang.org/go/cue/load"
|
||||
@@ -28,6 +29,8 @@ const (
|
||||
// Helm is the value of the kind field of holos build output indicating helm
|
||||
// values and helm command information.
|
||||
Helm = "HelmChart"
|
||||
// Skip is the value when the instance should be skipped
|
||||
Skip = "Skip"
|
||||
// ChartDir is the chart cache directory name.
|
||||
ChartDir = "vendor"
|
||||
)
|
||||
@@ -84,6 +87,7 @@ type Result struct {
|
||||
KsContent string `json:"ksContent,omitempty"`
|
||||
APIObjectMap apiObjectMap `json:"apiObjectMap,omitempty"`
|
||||
finalOutput string
|
||||
Skip bool
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
@@ -131,11 +135,27 @@ func (r *Result) FinalOutput() string {
|
||||
// addAPIObjects adds the overlay api objects to finalOutput.
|
||||
func (r *Result) addOverlayObjects(log *slog.Logger) {
|
||||
b := []byte(r.FinalOutput())
|
||||
for kind, v := range r.APIObjectMap {
|
||||
for name, yamlString := range v {
|
||||
kinds := make([]string, 0, len(r.APIObjectMap))
|
||||
// Sort the keys
|
||||
for kind := range r.APIObjectMap {
|
||||
kinds = append(kinds, kind)
|
||||
}
|
||||
slices.Sort(kinds)
|
||||
|
||||
for _, kind := range kinds {
|
||||
v := r.APIObjectMap[kind]
|
||||
// Sort the keys
|
||||
names := make([]string, 0, len(v))
|
||||
for name := range v {
|
||||
names = append(names, name)
|
||||
}
|
||||
slices.Sort(names)
|
||||
|
||||
for _, name := range names {
|
||||
yamlString := v[name]
|
||||
log.Debug(fmt.Sprintf("%s/%s", kind, name), "kind", kind, "name", name)
|
||||
util.EnsureNewline(b)
|
||||
header := fmt.Sprintf("---\n# Source: holos component cue api objects %s/%s\n", kind, name)
|
||||
header := fmt.Sprintf("---\n# Source: CUE apiObjects.%s.%s\n", kind, name)
|
||||
b = append(b, []byte(header+yamlString)...)
|
||||
util.EnsureNewline(b)
|
||||
}
|
||||
@@ -234,6 +254,8 @@ func (b *Builder) Run(ctx context.Context) (results []*Result, err error) {
|
||||
|
||||
log.DebugContext(ctx, "cue: processing holos component kind "+info.Kind)
|
||||
switch kind := info.Kind; kind {
|
||||
case Skip:
|
||||
result.Skip = true
|
||||
case Kube:
|
||||
// CUE directly provides the kubernetes api objects in result.Content
|
||||
if err := value.Decode(&result); err != nil {
|
||||
@@ -255,7 +277,6 @@ func (b *Builder) Run(ctx context.Context) (results []*Result, err error) {
|
||||
return nil, err
|
||||
}
|
||||
result.addOverlayObjects(log)
|
||||
|
||||
default:
|
||||
return nil, wrapper.Wrap(fmt.Errorf("build kind not implemented: %v", kind))
|
||||
}
|
||||
@@ -383,7 +404,7 @@ func isNotExist(path string) bool {
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
|
||||
// cacheChart stores a cached copy of Chart in the chart sub-directory of path.
|
||||
// cacheChart stores a cached copy of Chart in the chart subdirectory of path.
|
||||
func cacheChart(ctx context.Context, path holos.PathComponent, chartDir string, chart Chart) error {
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
0
|
||||
2
|
||||
|
||||
Reference in New Issue
Block a user