mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 16:54:58 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c977d22fe | ||
|
|
e34db2b583 | ||
|
|
71de57ac88 | ||
|
|
c7cc661018 | ||
|
|
09f39c02fe | ||
|
|
23c76a73e0 | ||
|
|
1cafe08237 | ||
|
|
45b07964ef | ||
|
|
6cc4a57b62 | ||
|
|
31280acbae | ||
|
|
6f0928b12c | ||
|
|
c6e9250d60 | ||
|
|
104bda459f | ||
|
|
bd2effa183 |
7
Makefile
7
Makefile
@@ -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
39
api/v1alpha1/buildplan.go
Normal 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 {
|
||||
HelmChartList []HelmChart `json:"helmChartList,omitempty" yaml:"helmChartList,omitempty"`
|
||||
KubernetesObjectsList []KubernetesObjects `json:"kubernetesObjectsList,omitempty" yaml:"kubernetesObjectsList,omitempty"`
|
||||
KustomizeBuildList []KustomizeBuild `json:"kustomizeBuildList,omitempty" yaml:"kustomizeBuildList,omitempty"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
22
api/v1alpha1/component.go
Normal file
22
api/v1alpha1/component.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package v1alpha1
|
||||
|
||||
// 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. 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
|
||||
// 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}
|
||||
}
|
||||
11
api/v1alpha1/constants.go
Normal file
11
api/v1alpha1/constants.go
Normal file
@@ -0,0 +1,11 @@
|
||||
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"
|
||||
// ResourcesFile is the file name used to store component output when post-processing with kustomize.
|
||||
ResourcesFile = "resources.yaml"
|
||||
)
|
||||
2
api/v1alpha1/doc.go
Normal file
2
api/v1alpha1/doc.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package v1alpha1 defines the api boundary between CUE and Holos.
|
||||
package v1alpha1
|
||||
154
api/v1alpha1/helm.go
Normal file
154
api/v1alpha1/helm.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
// Namespace is the namespace to install into. TODO: Use metadata.namespace instead.
|
||||
Namespace string `json:"namespace"`
|
||||
Chart Chart `json:"chart"`
|
||||
ValuesContent string `json:"valuesContent"`
|
||||
EnableHooks bool `json:"enableHooks"`
|
||||
}
|
||||
|
||||
type Chart struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Release string `json:"release"`
|
||||
Repository Repository `json:"repository,omitempty"`
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func (hc *HelmChart) Render(ctx context.Context, path holos.PathComponent) (*Result, error) {
|
||||
result := Result{HolosComponent: hc.HolosComponent}
|
||||
if err := hc.helm(ctx, &result, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.addObjectMap(ctx, hc.APIObjectMap)
|
||||
if err := result.kustomize(ctx); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not kustomize: %w", err))
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// runHelm provides the values produced by CUE to helm template and returns
|
||||
// the rendered kubernetes api objects in the result.
|
||||
func (hc *HelmChart) helm(ctx context.Context, r *Result, path holos.PathComponent) error {
|
||||
log := logger.FromContext(ctx).With("chart", hc.Chart.Name)
|
||||
if hc.Chart.Name == "" {
|
||||
log.WarnContext(ctx, "skipping helm: no chart name specified, use a different component type")
|
||||
return nil
|
||||
}
|
||||
|
||||
cachedChartPath := filepath.Join(string(path), ChartDir, filepath.Base(hc.Chart.Name))
|
||||
if isNotExist(cachedChartPath) {
|
||||
// Add repositories
|
||||
repo := hc.Chart.Repository
|
||||
if repo.URL != "" {
|
||||
out, err := util.RunCmd(ctx, "helm", "repo", "add", repo.Name, repo.URL)
|
||||
if err != nil {
|
||||
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
|
||||
return wrapper.Wrap(fmt.Errorf("could not run helm repo add: %w", err))
|
||||
}
|
||||
// Update repository
|
||||
out, err = util.RunCmd(ctx, "helm", "repo", "update", repo.Name)
|
||||
if err != nil {
|
||||
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
|
||||
return wrapper.Wrap(fmt.Errorf("could not run helm repo update: %w", err))
|
||||
}
|
||||
} else {
|
||||
log.DebugContext(ctx, "no chart repository url proceeding assuming oci chart")
|
||||
}
|
||||
|
||||
// Cache the chart
|
||||
if err := cacheChart(ctx, path, ChartDir, hc.Chart); err != nil {
|
||||
return fmt.Errorf("could not cache chart: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Write values file
|
||||
tempDir, err := os.MkdirTemp("", "holos")
|
||||
if err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not make temp dir: %w", err))
|
||||
}
|
||||
defer util.Remove(ctx, tempDir)
|
||||
|
||||
valuesPath := filepath.Join(tempDir, "values.yaml")
|
||||
if err := os.WriteFile(valuesPath, []byte(hc.ValuesContent), 0644); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not write values: %w", err))
|
||||
}
|
||||
log.DebugContext(ctx, "helm: wrote values", "path", valuesPath, "bytes", len(hc.ValuesContent))
|
||||
|
||||
// Run charts
|
||||
chart := hc.Chart
|
||||
args := []string{"template"}
|
||||
if !hc.EnableHooks {
|
||||
args = append(args, "--no-hooks")
|
||||
}
|
||||
namespace := hc.Namespace
|
||||
args = append(args, "--include-crds", "--values", valuesPath, "--namespace", namespace, "--kubeconfig", "/dev/null", "--version", chart.Version, chart.Release, cachedChartPath)
|
||||
helmOut, err := util.RunCmd(ctx, "helm", args...)
|
||||
if err != nil {
|
||||
stderr := helmOut.Stderr.String()
|
||||
lines := strings.Split(stderr, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "Error:") {
|
||||
err = fmt.Errorf("%s: %w", line, err)
|
||||
}
|
||||
}
|
||||
return wrapper.Wrap(fmt.Errorf("could not run helm template: %w", err))
|
||||
}
|
||||
|
||||
r.accumulatedOutput = helmOut.Stdout.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
cacheTemp, err := os.MkdirTemp(string(path), chartDir)
|
||||
if err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not make temp dir: %w", err))
|
||||
}
|
||||
defer util.Remove(ctx, cacheTemp)
|
||||
|
||||
chartName := chart.Name
|
||||
if chart.Repository.Name != "" {
|
||||
chartName = fmt.Sprintf("%s/%s", chart.Repository.Name, chart.Name)
|
||||
}
|
||||
helmOut, err := util.RunCmd(ctx, "helm", "pull", "--destination", cacheTemp, "--untar=true", "--version", chart.Version, chartName)
|
||||
if err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not run helm pull: %w", err))
|
||||
}
|
||||
log.Debug("helm pull", "stdout", helmOut.Stdout, "stderr", helmOut.Stderr)
|
||||
|
||||
cachePath := filepath.Join(string(path), chartDir)
|
||||
if err := os.Rename(cacheTemp, cachePath); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not rename: %w", err))
|
||||
}
|
||||
log.InfoContext(ctx, "cached", "chart", chart.Name, "version", chart.Version, "path", cachePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
func isNotExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
20
api/v1alpha1/kubernetesobjects.go
Normal file
20
api/v1alpha1/kubernetesobjects.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/holos-run/holos"
|
||||
)
|
||||
|
||||
const KubernetesObjectsKind = "KubernetesObjects"
|
||||
|
||||
// KubernetesObjects represents CUE output which directly provides Kubernetes api objects to holos.
|
||||
type KubernetesObjects struct {
|
||||
HolosComponent `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
// Render produces kubernetes api objects from the APIObjectMap
|
||||
func (o *KubernetesObjects) Render(ctx context.Context, path holos.PathComponent) (*Result, error) {
|
||||
result := Result{HolosComponent: o.HolosComponent}
|
||||
result.addObjectMap(ctx, o.APIObjectMap)
|
||||
return &result, nil
|
||||
}
|
||||
7
api/v1alpha1/kustomization.go
Normal file
7
api/v1alpha1/kustomization.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package v1alpha1
|
||||
|
||||
// Kustomization holds the rendered flux kustomization api object content for git ops.
|
||||
type Kustomization struct {
|
||||
// KsContent is the yaml representation of the flux kustomization for gitops.
|
||||
KsContent string `json:"ksContent,omitempty" yaml:"ksContent,omitempty"`
|
||||
}
|
||||
46
api/v1alpha1/kustomize.go
Normal file
46
api/v1alpha1/kustomize.go
Normal 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
|
||||
}
|
||||
8
api/v1alpha1/objectmap.go
Normal file
8
api/v1alpha1/objectmap.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package v1alpha1
|
||||
|
||||
// APIObjectMap is the shape of marshalled api objects returned from cue to the
|
||||
// holos cli. A map is used to improve the clarity of error messages from cue.
|
||||
type APIObjectMap map[string]map[string]string
|
||||
|
||||
// FileContentMap is a map of file names to file contents.
|
||||
type FileContentMap map[string]string
|
||||
15
api/v1alpha1/objectmeta.go
Normal file
15
api/v1alpha1/objectmeta.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package v1alpha1
|
||||
|
||||
// ObjectMeta represents metadata of a holos component object. The fields are a
|
||||
// copy of upstream kubernetes api machinery but are by holos objects distinct
|
||||
// from kubernetes api objects.
|
||||
type ObjectMeta struct {
|
||||
// Name uniquely identifies the holos component instance and must be suitable as a file name.
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
// Namespace confines a holos component to a single namespace via kustomize if set.
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
// Labels are not used but are copied from api machinery ObjectMeta for completeness.
|
||||
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
||||
// Annotations are not used but are copied from api machinery ObjectMeta for completeness.
|
||||
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
|
||||
}
|
||||
21
api/v1alpha1/render.go
Normal file
21
api/v1alpha1/render.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/holos-run/holos"
|
||||
)
|
||||
|
||||
type Renderer interface {
|
||||
GetKind() string
|
||||
Render(ctx context.Context, path holos.PathComponent) (*Result, error)
|
||||
}
|
||||
|
||||
// Render produces a Result representing the kubernetes api objects to
|
||||
// configure. Each of the various holos component types, e.g. Helm, Kustomize,
|
||||
// et al, should implement the Renderer interface. This process is best
|
||||
// conceptualized as a data pipeline, for example a component may render a
|
||||
// result by first calling helm template, then passing the result through
|
||||
// kustomize, then mixing in overlay api objects.
|
||||
func Render(ctx context.Context, r Renderer, path holos.PathComponent) (*Result, error) {
|
||||
return r.Render(ctx, path)
|
||||
}
|
||||
137
api/v1alpha1/result.go
Normal file
137
api/v1alpha1/result.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"github.com/holos-run/holos/pkg/util"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// Result is the build result for display or writing. Holos components Render the Result as a data pipeline.
|
||||
type Result struct {
|
||||
HolosComponent
|
||||
// accumulatedOutput accumulates rendered api objects.
|
||||
accumulatedOutput string
|
||||
}
|
||||
|
||||
func (r *Result) Name() string {
|
||||
return r.Metadata.Name
|
||||
}
|
||||
|
||||
func (r *Result) Filename(writeTo string, cluster string) string {
|
||||
name := r.Metadata.Name
|
||||
return filepath.Join(writeTo, "clusters", cluster, "components", name, name+".gen.yaml")
|
||||
}
|
||||
|
||||
func (r *Result) KustomizationFilename(writeTo string, cluster string) string {
|
||||
return filepath.Join(writeTo, "clusters", cluster, "holos", "components", r.Metadata.Name+"-kustomization.gen.yaml")
|
||||
}
|
||||
|
||||
// AccumulatedOutput returns the accumulated rendered output.
|
||||
func (r *Result) AccumulatedOutput() string {
|
||||
return r.accumulatedOutput
|
||||
}
|
||||
|
||||
// addObjectMap renders the provided APIObjectMap into the accumulated output.
|
||||
func (r *Result) addObjectMap(ctx context.Context, objectMap APIObjectMap) {
|
||||
log := logger.FromContext(ctx)
|
||||
b := []byte(r.AccumulatedOutput())
|
||||
kinds := make([]string, 0, len(objectMap))
|
||||
// Sort the keys
|
||||
for kind := range objectMap {
|
||||
kinds = append(kinds, kind)
|
||||
}
|
||||
slices.Sort(kinds)
|
||||
|
||||
for _, kind := range kinds {
|
||||
v := objectMap[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)
|
||||
b = util.EnsureNewline(b)
|
||||
header := fmt.Sprintf("---\n# Source: CUE apiObjects.%s.%s\n", kind, name)
|
||||
b = append(b, []byte(header+yamlString)...)
|
||||
b = util.EnsureNewline(b)
|
||||
}
|
||||
}
|
||||
r.accumulatedOutput = string(b)
|
||||
}
|
||||
|
||||
// kustomize replaces the accumulated output with the output of kustomize build
|
||||
func (r *Result) kustomize(ctx context.Context) error {
|
||||
log := logger.FromContext(ctx)
|
||||
if r.ResourcesFile == "" {
|
||||
log.DebugContext(ctx, "skipping kustomize: no resourcesFile")
|
||||
return nil
|
||||
}
|
||||
if len(r.KustomizeFiles) < 1 {
|
||||
log.DebugContext(ctx, "skipping kustomize: no kustomizeFiles")
|
||||
return nil
|
||||
}
|
||||
tempDir, err := os.MkdirTemp("", "holos.kustomize")
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
defer util.Remove(ctx, tempDir)
|
||||
|
||||
// Write the main api object resources file for kustomize.
|
||||
target := filepath.Join(tempDir, r.ResourcesFile)
|
||||
b := []byte(r.AccumulatedOutput())
|
||||
b = util.EnsureNewline(b)
|
||||
if err := os.WriteFile(target, b, 0644); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not write resources: %w", err))
|
||||
}
|
||||
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
|
||||
|
||||
// Write the kustomization tree, kustomization.yaml must be in this map for kustomize to work.
|
||||
for file, content := range r.KustomizeFiles {
|
||||
target := filepath.Join(tempDir, file)
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
b := []byte(content)
|
||||
b = util.EnsureNewline(b)
|
||||
if err := os.WriteFile(target, b, 0644); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not write: %w", err))
|
||||
}
|
||||
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
|
||||
}
|
||||
|
||||
// Run kustomize.
|
||||
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", tempDir)
|
||||
if err != nil {
|
||||
log.ErrorContext(ctx, kOut.Stderr.String())
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
// Replace the accumulated output
|
||||
r.accumulatedOutput = kOut.Stdout.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save writes the content to the filesystem for git ops.
|
||||
func (r *Result) Save(ctx context.Context, path string, content string) error {
|
||||
log := logger.FromContext(ctx)
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, os.FileMode(0775)); err != nil {
|
||||
log.WarnContext(ctx, "could not mkdir", "path", dir, "err", err)
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
// Write the kube api objects
|
||||
if err := os.WriteFile(path, []byte(content), os.FileMode(0644)); err != nil {
|
||||
log.WarnContext(ctx, "could not write", "path", path, "err", err)
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
log.DebugContext(ctx, "out: wrote "+path, "action", "write", "path", path, "status", "ok")
|
||||
return nil
|
||||
}
|
||||
10
api/v1alpha1/typemeta.go
Normal file
10
api/v1alpha1/typemeta.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package v1alpha1
|
||||
|
||||
type TypeMeta struct {
|
||||
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||
}
|
||||
|
||||
func (tm *TypeMeta) GetKind() string {
|
||||
return tm.Kind
|
||||
}
|
||||
29
cmd/holos/testdata/constraints.txt
vendored
29
cmd/holos/testdata/constraints.txt
vendored
@@ -1,7 +1,6 @@
|
||||
# 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
|
||||
@@ -12,31 +11,21 @@ metadata: name: "jeff"
|
||||
-- foo/bar/bar.cue --
|
||||
package holos
|
||||
|
||||
#KubernetesObjects & {
|
||||
apiObjectMap: foo: bar: "bf2bc7f9-9ba0-4f9e-9bd2-9a205627eb0b"
|
||||
}
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#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
|
||||
}
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
#KubernetesObjects: {
|
||||
#OutputTypeMeta
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "KubernetesObjects"
|
||||
apiObjectMap: {...}
|
||||
}
|
||||
|
||||
#NoOutput: {
|
||||
#OutputTypeMeta
|
||||
kind: string | *"Skip"
|
||||
metadata: name: string | *"skipped"
|
||||
}
|
||||
|
||||
#NoOutput & {}
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
|
||||
15
cmd/holos/testdata/issue15_cue_errors.txt
vendored
15
cmd/holos/testdata/issue15_cue_errors.txt
vendored
@@ -1,16 +1,17 @@
|
||||
# Want cue errors to show files and lines
|
||||
! exec holos build .
|
||||
stderr '^apiObjectMap.foo.bar: cannot convert non-concrete value string'
|
||||
stderr '/component.cue:7:20$'
|
||||
stderr 'apiObjectMap.foo.bar: cannot convert incomplete value'
|
||||
stderr '/component.cue:\d+:\d+$'
|
||||
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "KubernetesObjects"
|
||||
cluster: string @tag(cluster, string)
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
apiObjectMap: foo: bar: baz
|
||||
baz: string
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
spec: components: KubernetesObjectsList: [{apiObjectMap: foo: bar: _baz}]
|
||||
|
||||
_baz: string
|
||||
|
||||
@@ -9,15 +9,17 @@ package holos
|
||||
package holos
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "KubernetesObjects"
|
||||
cluster: string @tag(cluster, string)
|
||||
kind: "BuildPlan"
|
||||
spec: components: KubernetesObjectsList: [{apiObjectMap: #APIObjects.apiObjectMap}]
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
#SecretStore: {
|
||||
kind: string
|
||||
metadata: name: string
|
||||
}
|
||||
|
||||
#APIObjects & {
|
||||
#APIObjects: {
|
||||
apiObjects: {
|
||||
SecretStore: {
|
||||
default: #SecretStore & { metadata: name: "default" }
|
||||
@@ -54,4 +56,3 @@ import "encoding/yaml"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,15 +10,17 @@ package holos
|
||||
package holos
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "HelmChart"
|
||||
cluster: string @tag(cluster, string)
|
||||
kind: "BuildPlan"
|
||||
spec: components: HelmChartList: [{apiObjectMap: #APIObjects.apiObjectMap}]
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
#SecretStore: {
|
||||
kind: string
|
||||
metadata: name: string
|
||||
}
|
||||
|
||||
#APIObjects & {
|
||||
#APIObjects: {
|
||||
apiObjects: {
|
||||
SecretStore: {
|
||||
default: #SecretStore & { metadata: name: "default" }
|
||||
@@ -55,4 +57,3 @@ import "encoding/yaml"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
cmd/holos/testdata/issue33_helm_stderr.txt
vendored
33
cmd/holos/testdata/issue33_helm_stderr.txt
vendored
@@ -7,22 +7,27 @@ package holos
|
||||
-- zitadel.cue --
|
||||
package holos
|
||||
|
||||
cluster: string @tag(cluster, string)
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "HelmChart"
|
||||
metadata: name: "zitadel"
|
||||
namespace: "zitadel"
|
||||
chart: {
|
||||
name: "zitadel"
|
||||
version: "7.9.0"
|
||||
release: name
|
||||
repository: {
|
||||
name: "zitadel"
|
||||
url: "https://charts.zitadel.com"
|
||||
}
|
||||
}
|
||||
kind: "BuildPlan"
|
||||
spec: components: HelmChartList: [_HelmChart]
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
_HelmChart: {
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "HelmChart"
|
||||
metadata: name: "zitadel"
|
||||
namespace: "zitadel"
|
||||
chart: {
|
||||
name: "zitadel"
|
||||
version: "7.9.0"
|
||||
release: name
|
||||
repository: {
|
||||
name: "zitadel"
|
||||
url: "https://charts.zitadel.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-- vendor/zitadel/templates/secret_zitadel-masterkey.yaml --
|
||||
{{- if (or (and .Values.zitadel.masterkey .Values.zitadel.masterkeySecretName) (and (not .Values.zitadel.masterkey) (not .Values.zitadel.masterkeySecretName)) ) }}
|
||||
|
||||
@@ -9,22 +9,25 @@ package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
|
||||
cluster: string @tag(cluster, string)
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "KustomizeBuild"
|
||||
metadata: name: "kstest"
|
||||
kind: "BuildPlan"
|
||||
spec: components: KustomizeBuildList: [{metadata: name: "kstest"}]
|
||||
|
||||
-- kustomization.yaml --
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: mynamespace
|
||||
resources:
|
||||
- serviceaccount.yaml
|
||||
|
||||
-- serviceaccount.yaml --
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test
|
||||
|
||||
-- want.yaml --
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
|
||||
14
cmd/holos/testdata/issue72_disallow_unknown_fields.txt
vendored
Normal file
14
cmd/holos/testdata/issue72_disallow_unknown_fields.txt
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# https://github.com/holos-run/holos/issues/72
|
||||
# Want holos to fail on unknown fields to catch typos and aid refactors
|
||||
! exec holos build .
|
||||
stderr 'unknown field \\"TypoKubernetesObjectsList\\"'
|
||||
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
spec: components: TypoKubernetesObjectsList: []
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// 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. 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
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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"
|
||||
|
||||
// ResourcesFile is the file name used to store component output when post processing with kustomize.
|
||||
#ResourcesFile: "resources.yaml"
|
||||
@@ -0,0 +1,6 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
|
||||
|
||||
// Package v1alpha1 defines the api boundary between CUE and Holos.
|
||||
package v1alpha1
|
||||
@@ -0,0 +1,28 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// A HelmChart represents a helm command to provide chart values in order to render kubernetes api objects.
|
||||
#HelmChart: {
|
||||
#HolosComponent
|
||||
|
||||
// Namespace is the namespace to install into. TODO: Use metadata.namespace instead.
|
||||
namespace: string @go(Namespace)
|
||||
chart: #Chart @go(Chart)
|
||||
valuesContent: string @go(ValuesContent)
|
||||
enableHooks: bool @go(EnableHooks)
|
||||
}
|
||||
|
||||
#Chart: {
|
||||
name: string @go(Name)
|
||||
version: string @go(Version)
|
||||
release: string @go(Release)
|
||||
repository?: #Repository @go(Repository)
|
||||
}
|
||||
|
||||
#Repository: {
|
||||
name: string @go(Name)
|
||||
url: string @go(URL)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
#KubernetesObjectsKind: "KubernetesObjects"
|
||||
|
||||
// KubernetesObjects represents CUE output which directly provides Kubernetes api objects to holos.
|
||||
#KubernetesObjects: {
|
||||
#HolosComponent
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// Kustomization holds the rendered flux kustomization api object content for git ops.
|
||||
#Kustomization: {
|
||||
// KsContent is the yaml representation of the flux kustomization for gitops.
|
||||
ksContent?: string @go(KsContent)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
#KustomizeBuildKind: "KustomizeBuild"
|
||||
|
||||
// KustomizeBuild
|
||||
#KustomizeBuild: {
|
||||
#HolosComponent
|
||||
}
|
||||
@@ -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
|
||||
|
||||
// APIObjectMap is the shape of marshalled api objects returned from cue to the
|
||||
// holos cli. A map is used to improve the clarity of error messages from cue.
|
||||
#APIObjectMap: {[string]: [string]: string}
|
||||
|
||||
// FileContentMap is a map of file names to file contents.
|
||||
#FileContentMap: {[string]: string}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// ObjectMeta represents metadata of a holos component object. The fields are a
|
||||
// copy of upstream kubernetes api machinery but are by holos objects distinct
|
||||
// from kubernetes api objects.
|
||||
#ObjectMeta: {
|
||||
// Name uniquely identifies the holos component instance and must be suitable as a file name.
|
||||
name?: string @go(Name)
|
||||
|
||||
// Namespace confines a holos component to a single namespace via kustomize if set.
|
||||
namespace?: string @go(Namespace)
|
||||
|
||||
// Labels are not used but are copied from api machinery ObjectMeta for completeness.
|
||||
labels?: {[string]: string} @go(Labels,map[string]string)
|
||||
|
||||
// Annotations are not used but are copied from api machinery ObjectMeta for completeness.
|
||||
annotations?: {[string]: string} @go(Annotations,map[string]string)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
|
||||
|
||||
package v1alpha1
|
||||
|
||||
#Renderer: _
|
||||
@@ -0,0 +1,10 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// Result is the build result for display or writing. Holos components Render the Result as a data pipeline.
|
||||
#Result: {
|
||||
HolosComponent: #HolosComponent
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
|
||||
|
||||
package v1alpha1
|
||||
|
||||
#TypeMeta: {
|
||||
kind?: string @go(Kind)
|
||||
apiVersion?: string @go(APIVersion)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package v1alpha1
|
||||
@@ -0,0 +1,5 @@
|
||||
package v1alpha1
|
||||
|
||||
#HolosComponent: Skip: true | *false
|
||||
|
||||
#HelmChart: enableHooks: true | *false
|
||||
31
docs/examples/helpers.cue
Normal file
31
docs/examples/helpers.cue
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
}
|
||||
117
docs/examples/holos.cue
Normal file
117
docs/examples/holos.cue
Normal file
@@ -0,0 +1,117 @@
|
||||
package holos
|
||||
|
||||
import (
|
||||
"encoding/yaml"
|
||||
h "github.com/holos-run/holos/api/v1alpha1"
|
||||
kc "sigs.k8s.io/kustomize/api/types"
|
||||
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
|
||||
|
||||
let DependsOn = {[Name=_]: name: string & Name}
|
||||
|
||||
// #HolosComponent defines struct fields common to all holos component types.
|
||||
#HolosComponent: {
|
||||
h.#HolosComponent
|
||||
_dependsOn: DependsOn
|
||||
let DEPENDS_ON = _dependsOn
|
||||
metadata: name: string
|
||||
#namelen: len(metadata.name) & >=1
|
||||
let Name = metadata.name
|
||||
|
||||
// TODO: ksContent needs to be component scoped, not instance scoped.
|
||||
ksContent: yaml.Marshal(#Kustomization & {
|
||||
_dependsOn: DEPENDS_ON
|
||||
metadata: name: Name
|
||||
})
|
||||
// Leave the HolosComponent open for components with additional fields like HelmChart.
|
||||
// Refer to https://cuelang.org/docs/tour/types/closed/
|
||||
...
|
||||
}
|
||||
|
||||
//#KustomizeFiles represents resources for holos to write into files for kustomize post-processing.
|
||||
#KustomizeFiles: {
|
||||
// Objects collects files for Holos to write for kustomize post-processing.
|
||||
Objects: "kustomization.yaml": #Kustomize
|
||||
// Files holds the marshaled output of Objects holos writes to the filesystem before calling the kustomize post-processor.
|
||||
Files: {
|
||||
for filename, obj in Objects {
|
||||
"\(filename)": yaml.Marshal(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Holos component types.
|
||||
#HelmChart: #HolosComponent & h.#HelmChart & {
|
||||
_values: {...}
|
||||
_kustomizeFiles: #KustomizeFiles
|
||||
|
||||
// Render the values to yaml for holos to provide to helm.
|
||||
valuesContent: yaml.Marshal(_values)
|
||||
// Kustomize post-processor
|
||||
// resources is the intermediate file name for api objects.
|
||||
resourcesFile: h.#ResourcesFile
|
||||
// kustomizeFiles represents the files in a kustomize directory tree.
|
||||
kustomizeFiles: _kustomizeFiles.Files
|
||||
|
||||
chart: h.#Chart & {
|
||||
name: string
|
||||
release: string | *name
|
||||
}
|
||||
}
|
||||
#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: DependsOn
|
||||
|
||||
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}, ...]
|
||||
}
|
||||
}
|
||||
|
||||
// #Kustomize represents the kustomize post processor.
|
||||
#Kustomize: kc.#Kustomization & {
|
||||
_patches: {[_]: kc.#Patch}
|
||||
apiVersion: "kustomize.config.k8s.io/v1beta1"
|
||||
kind: "Kustomization"
|
||||
// resources are file names holos will use to store intermediate component output for kustomize to post-process (i.e. helm template | kubectl kustomize)
|
||||
// See the related resourcesFile field of the holos component.
|
||||
resources: [h.#ResourcesFile]
|
||||
if len(_patches) > 0 {
|
||||
patches: [for v in _patches {v}]
|
||||
}
|
||||
}
|
||||
|
||||
// So components don't need to import the package.
|
||||
#Patch: kc.#Patch
|
||||
@@ -1,13 +1,39 @@
|
||||
package holos
|
||||
|
||||
#InputKeys: component: "postgres-certs"
|
||||
#KubernetesObjects & {
|
||||
|
||||
let SecretNames = {
|
||||
[Name=_]: {name: Name}
|
||||
"\(_DBName)-primary-tls": _
|
||||
"\(_DBName)-repl-tls": _
|
||||
"\(_DBName)-client-tls": _
|
||||
"\(_DBName)-root-ca": _
|
||||
}
|
||||
|
||||
#Kustomization: spec: targetNamespace: #TargetNamespace
|
||||
#Kustomization: spec: healthChecks: [
|
||||
for s in SecretNames {
|
||||
apiVersion: "external-secrets.io/v1beta1"
|
||||
kind: "ExternalSecret"
|
||||
name: s.name
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
]
|
||||
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-iam-postgres-certs"
|
||||
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: {
|
||||
"\(_DBName)-primary-tls": _
|
||||
"\(_DBName)-repl-tls": _
|
||||
"\(_DBName)-client-tls": _
|
||||
"\(_DBName)-root-ca": _
|
||||
for s in SecretNames {
|
||||
ExternalSecret: "\(s.name)": _
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,34 @@ let BucketRepoName = "repo2"
|
||||
// Restore the most recent backup.
|
||||
let RestoreOptions = []
|
||||
|
||||
#KubernetesObjects & {
|
||||
#Kustomization: spec: healthChecks: [
|
||||
{
|
||||
apiVersion: "external-secrets.io/v1beta1"
|
||||
kind: "ExternalSecret"
|
||||
name: S3Secret
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
{
|
||||
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
|
||||
kind: "PostgresCluster"
|
||||
name: _DBName
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
]
|
||||
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-iam-postgres"
|
||||
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-iam-postgres-certs": _
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "pgo-s3-creds": _
|
||||
ExternalSecret: "\(S3Secret)": _
|
||||
PostgresCluster: db: #PostgresCluster & HighlyAvailable & {
|
||||
metadata: name: _DBName
|
||||
metadata: namespace: #TargetNamespace
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package holos
|
||||
|
||||
#InstancePrefix: "prod-iam"
|
||||
#TargetNamespace: #InstancePrefix + "-zitadel"
|
||||
|
||||
// _DBName is the database name used across multiple holos components in this project
|
||||
|
||||
@@ -4,24 +4,30 @@ import "encoding/yaml"
|
||||
|
||||
let Name = "zitadel"
|
||||
#InputKeys: component: Name
|
||||
#DependsOn: postgres: _
|
||||
|
||||
// Upstream helm chart doesn't specify the namespace field for all resources.
|
||||
#Kustomization: spec: targetNamespace: #TargetNamespace
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
metadata: name: "\(#InstancePrefix)-zitadel"
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
enableHooks: true
|
||||
chart: {
|
||||
name: Name
|
||||
version: "7.9.0"
|
||||
repository: {
|
||||
name: Name
|
||||
url: "https://charts.zitadel.com"
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
_dependsOn: "\(#InstancePrefix)-postgres": _
|
||||
|
||||
namespace: #TargetNamespace
|
||||
enableHooks: true
|
||||
chart: {
|
||||
name: Name
|
||||
version: "7.9.0"
|
||||
repository: {
|
||||
name: Name
|
||||
url: "https://charts.zitadel.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
values: #Values
|
||||
_values: #Values
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "zitadel-masterkey": _
|
||||
VirtualService: "\(Name)": {
|
||||
@@ -71,7 +77,7 @@ let CAPatch = #Patch & {
|
||||
patch: yaml.Marshal(DatabaseCACertPatch)
|
||||
}
|
||||
|
||||
#KustomizePatches: {
|
||||
#Kustomize: _patches: {
|
||||
mesh: {
|
||||
target: {
|
||||
group: "apps"
|
||||
@@ -96,6 +102,14 @@ let CAPatch = #Patch & {
|
||||
target: kind: "Job"
|
||||
target: name: "\(Name)-setup"
|
||||
}
|
||||
testDisable: {
|
||||
target: {
|
||||
version: "v1"
|
||||
kind: "Pod"
|
||||
name: "\(Name)-test-connection"
|
||||
}
|
||||
patch: yaml.Marshal(DisableFluxPatch)
|
||||
}
|
||||
if #IsPrimaryCluster == false {
|
||||
fluxDisable: {
|
||||
target: {
|
||||
@@ -128,3 +142,32 @@ let CAPatch = #Patch & {
|
||||
}
|
||||
|
||||
let DisableFluxPatch = [{op: "replace", path: "/metadata/annotations/kustomize.toolkit.fluxcd.io~1reconcile", value: "disabled"}]
|
||||
|
||||
// Upstream helm chart doesn't specify the namespace field for all resources.
|
||||
#Kustomization: spec: {
|
||||
targetNamespace: #TargetNamespace
|
||||
wait: false
|
||||
}
|
||||
|
||||
if #IsPrimaryCluster == true {
|
||||
#Kustomization: spec: healthChecks: [
|
||||
{
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
name: Name
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
{
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
name: "\(Name)-init"
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
{
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
name: "\(Name)-setup"
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,32 +9,43 @@ let GitHubConfigSecret = "controller-manager"
|
||||
// Just sync the external secret, don't configure the scale set
|
||||
// Work around https://github.com/actions/actions-runner-controller/issues/3351
|
||||
if #IsPrimaryCluster == false {
|
||||
#KubernetesObjects & {
|
||||
apiObjects: ExternalSecret: "\(GitHubConfigSecret)": _
|
||||
}
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-github-arc-runner"
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: ExternalSecret: "\(GitHubConfigSecret)": _
|
||||
}).apiObjectMap
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Put the scale set on the primary cluster.
|
||||
if #IsPrimaryCluster == true {
|
||||
#HelmChart & {
|
||||
values: {
|
||||
#Values
|
||||
controllerServiceAccount: name: "gha-rs-controller"
|
||||
controllerServiceAccount: namespace: "arc-system"
|
||||
githubConfigSecret: GitHubConfigSecret
|
||||
githubConfigUrl: "https://github.com/" + #Platform.org.github.orgs.primary.name
|
||||
}
|
||||
apiObjects: ExternalSecret: "\(values.githubConfigSecret)": _
|
||||
chart: {
|
||||
// Match the gha-base-name in the chart _helpers.tpl to avoid long full names.
|
||||
// NOTE: Unfortunately the INSTALLATION_NAME is used as the helm release
|
||||
// name and GitHub removed support for runner labels, so the only way to
|
||||
// specify which runner a workflow runs on is using this helm release name.
|
||||
// The quote is "Update the INSTALLATION_NAME value carefully. You will use
|
||||
// the installation name as the value of runs-on in your workflows." Refer to
|
||||
// https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/quickstart-for-actions-runner-controller
|
||||
release: "gha-rs"
|
||||
name: "oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set"
|
||||
}
|
||||
}
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
metadata: name: "prod-github-arc-runner"
|
||||
_values: {
|
||||
#Values
|
||||
controllerServiceAccount: name: "gha-rs-controller"
|
||||
controllerServiceAccount: namespace: "arc-system"
|
||||
githubConfigSecret: GitHubConfigSecret
|
||||
githubConfigUrl: "https://github.com/" + #Platform.org.github.orgs.primary.name
|
||||
}
|
||||
apiObjectMap: (#APIObjects & {apiObjects: ExternalSecret: "\(_values.githubConfigSecret)": _}).apiObjectMap
|
||||
chart: {
|
||||
// Match the gha-base-name in the chart _helpers.tpl to avoid long full names.
|
||||
// NOTE: Unfortunately the INSTALLATION_NAME is used as the helm release
|
||||
// name and GitHub removed support for runner labels, so the only way to
|
||||
// specify which runner a workflow runs on is using this helm release name.
|
||||
// The quote is "Update the INSTALLATION_NAME value carefully. You will use
|
||||
// the installation name as the value of runs-on in your workflows." Refer to
|
||||
// https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/quickstart-for-actions-runner-controller
|
||||
release: "gha-rs"
|
||||
name: "oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,13 +3,18 @@ package holos
|
||||
#TargetNamespace: #ARCSystemNamespace
|
||||
#InputKeys: component: "arc-system"
|
||||
|
||||
#HelmChart & {
|
||||
values: #Values & #DefaultSecurityContext
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
// Match the gha-base-name in the chart _helpers.tpl to avoid long full names.
|
||||
release: "gha-rs-controller"
|
||||
name: "oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller"
|
||||
version: "0.8.3"
|
||||
}
|
||||
}
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
metadata: name: "prod-github-arc-system"
|
||||
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_values: #Values & #DefaultSecurityContext
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
// Match the gha-base-name in the chart _helpers.tpl to avoid long full names.
|
||||
release: "gha-rs-controller"
|
||||
name: "oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller"
|
||||
version: "0.8.3"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
@@ -2,25 +2,23 @@ package holos
|
||||
|
||||
import "list"
|
||||
|
||||
#TargetNamespace: "default"
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#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
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package holos
|
||||
|
||||
#InputKeys: component: "istio-base"
|
||||
#TargetNamespace: "istio-system"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "base"
|
||||
version: "1.20.3"
|
||||
repository: {
|
||||
name: "istio"
|
||||
url: "https://istio-release.storage.googleapis.com/charts"
|
||||
metadata: name: "prod-mesh-istio-base"
|
||||
namespace: "istio-system"
|
||||
chart: {
|
||||
name: "base"
|
||||
version: "1.20.3"
|
||||
repository: {
|
||||
name: "istio"
|
||||
url: "https://istio-release.storage.googleapis.com/charts"
|
||||
}
|
||||
}
|
||||
}
|
||||
values: #IstioValues
|
||||
}
|
||||
_values: #IstioValues
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package holos
|
||||
|
||||
#InputKeys: component: "cni"
|
||||
#TargetNamespace: "kube-system"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-mesh-istio-base": _
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: name: "cni"
|
||||
values: #IstioValues
|
||||
}
|
||||
_values: #IstioValues
|
||||
metadata: name: "\(#InstancePrefix)-\(chart.name)"
|
||||
namespace: "kube-system"
|
||||
chart: name: "cni"
|
||||
},
|
||||
]
|
||||
|
||||
@@ -4,15 +4,23 @@ import "list"
|
||||
|
||||
// The primary istio Gateway, named default
|
||||
let Name = "gateway"
|
||||
|
||||
#InputKeys: component: Name
|
||||
|
||||
#TargetNamespace: "istio-ingress"
|
||||
#DependsOn: _IngressGateway
|
||||
|
||||
let LoginCert = #PlatformCerts.login
|
||||
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-mesh-istio-base": _
|
||||
_dependsOn: "prod-mesh-ingress": _
|
||||
|
||||
metadata: name: "\(#InstancePrefix)-\(Name)"
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: login: #ExternalSecret & {
|
||||
_name: "login"
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package holos
|
||||
|
||||
let Name = "httpbin"
|
||||
let ComponentName = "\(#InstancePrefix)-\(Name)"
|
||||
|
||||
let SecretName = #InputKeys.cluster + "-" + Name
|
||||
let MatchLabels = {app: Name} & #SelectorLabels
|
||||
let MatchLabels = {
|
||||
app: Name
|
||||
"app.kubernetes.io/instance": ComponentName
|
||||
}
|
||||
let Metadata = {
|
||||
name: Name
|
||||
namespace: #TargetNamespace
|
||||
@@ -12,11 +17,22 @@ let Metadata = {
|
||||
#InputKeys: component: Name
|
||||
|
||||
#TargetNamespace: "istio-ingress"
|
||||
#DependsOn: _IngressGateway
|
||||
|
||||
let Cert = #PlatformCerts[SecretName]
|
||||
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "\(#InstancePrefix)-istio-base": _
|
||||
_dependsOn: "\(#InstancePrefix)-ingress": _
|
||||
|
||||
metadata: name: ComponentName
|
||||
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "\(Cert.spec.secretName)": _
|
||||
Deployment: httpbin: #Deployment & {
|
||||
@@ -24,7 +40,6 @@ let Cert = #PlatformCerts[SecretName]
|
||||
spec: selector: matchLabels: MatchLabels
|
||||
spec: template: {
|
||||
metadata: labels: MatchLabels
|
||||
metadata: labels: #CommonLabels
|
||||
metadata: labels: #IstioSidecar
|
||||
spec: securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
spec: containers: [{
|
||||
@@ -54,7 +69,7 @@ let Cert = #PlatformCerts[SecretName]
|
||||
spec: servers: [
|
||||
{
|
||||
hosts: [for host in Cert.spec.dnsNames {"\(#TargetNamespace)/\(host)"}]
|
||||
port: name: "https-\(#InstanceName)"
|
||||
port: name: "https-\(ComponentName)"
|
||||
port: number: 443
|
||||
port: protocol: "HTTPS"
|
||||
tls: credentialName: Cert.spec.secretName
|
||||
|
||||
@@ -2,50 +2,58 @@ package holos
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
#InputKeys: component: "ingress"
|
||||
#TargetNamespace: "istio-ingress"
|
||||
#DependsOn: _IstioD
|
||||
let ComponentName = "\(#InstancePrefix)-ingress"
|
||||
|
||||
#HelmChart & {
|
||||
chart: name: "gateway"
|
||||
namespace: #TargetNamespace
|
||||
values: #GatewayValues & {
|
||||
// This component expects the load balancer to send the PROXY protocol header.
|
||||
// Refer to: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/service/annotations/#proxy-protocol-v2
|
||||
podAnnotations: "proxy.istio.io/config": json.Marshal(_ProxyProtocol)
|
||||
// TODO This configuration is specific to the OIS Metal NLB, refactor it out to the metal collection.
|
||||
service: {
|
||||
type: "NodePort"
|
||||
annotations: "service.beta.kubernetes.io/aws-load-balancer-proxy-protocol": "*"
|
||||
externalTrafficPolicy: "Local"
|
||||
// Add 30000 to the port to get the Nodeport
|
||||
ports: [
|
||||
{
|
||||
name: "status-port"
|
||||
port: 15021
|
||||
protocol: "TCP"
|
||||
targetPort: 15021
|
||||
nodePort: 30021
|
||||
},
|
||||
{
|
||||
name: "http2"
|
||||
port: 80
|
||||
protocol: "TCP"
|
||||
targetPort: 80
|
||||
nodePort: 30080
|
||||
},
|
||||
{
|
||||
name: "https"
|
||||
port: 443
|
||||
protocol: "TCP"
|
||||
targetPort: 443
|
||||
nodePort: 30443
|
||||
},
|
||||
]
|
||||
#TargetNamespace: "istio-ingress"
|
||||
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "\(#InstancePrefix)-istio-base": _
|
||||
_dependsOn: "\(#InstancePrefix)-istiod": _
|
||||
|
||||
metadata: name: ComponentName
|
||||
|
||||
chart: name: "gateway"
|
||||
namespace: #TargetNamespace
|
||||
_values: #GatewayValues & {
|
||||
// This component expects the load balancer to send the PROXY protocol header.
|
||||
// Refer to: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/service/annotations/#proxy-protocol-v2
|
||||
podAnnotations: "proxy.istio.io/config": json.Marshal(_ProxyProtocol)
|
||||
// TODO This configuration is specific to the OIS Metal NLB, refactor it out to the metal collection.
|
||||
service: {
|
||||
type: "NodePort"
|
||||
annotations: "service.beta.kubernetes.io/aws-load-balancer-proxy-protocol": "*"
|
||||
externalTrafficPolicy: "Local"
|
||||
// Add 30000 to the port to get the Nodeport
|
||||
ports: [
|
||||
{
|
||||
name: "status-port"
|
||||
port: 15021
|
||||
protocol: "TCP"
|
||||
targetPort: 15021
|
||||
nodePort: 30021
|
||||
},
|
||||
{
|
||||
name: "http2"
|
||||
port: 80
|
||||
protocol: "TCP"
|
||||
targetPort: 80
|
||||
nodePort: 30080
|
||||
},
|
||||
{
|
||||
name: "https"
|
||||
port: 443
|
||||
protocol: "TCP"
|
||||
targetPort: 443
|
||||
nodePort: 30443
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
apiObjects: _APIObjects
|
||||
}
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
_ProxyProtocol: gatewayTopology: proxyProtocol: {}
|
||||
|
||||
@@ -60,36 +68,82 @@ let RedirectMetaName = {
|
||||
namespace: #TargetNamespace
|
||||
}
|
||||
|
||||
// https-redirect
|
||||
_APIObjects: {
|
||||
Gateway: {
|
||||
"\(RedirectMetaName.name)": #Gateway & {
|
||||
metadata: RedirectMetaName
|
||||
spec: selector: GatewayLabels
|
||||
spec: servers: [{
|
||||
port: {
|
||||
number: 80
|
||||
name: "http2"
|
||||
protocol: "HTTP2"
|
||||
}
|
||||
hosts: ["*"]
|
||||
// handled by the VirtualService
|
||||
tls: httpsRedirect: false
|
||||
}]
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
Gateway: {
|
||||
"\(RedirectMetaName.name)": #Gateway & {
|
||||
metadata: RedirectMetaName
|
||||
spec: selector: GatewayLabels
|
||||
spec: servers: [{
|
||||
port: {
|
||||
number: 80
|
||||
name: "http2"
|
||||
protocol: "HTTP2"
|
||||
}
|
||||
hosts: ["*"]
|
||||
// handled by the VirtualService
|
||||
tls: httpsRedirect: false
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
VirtualService: {
|
||||
"\(RedirectMetaName.name)": #VirtualService & {
|
||||
metadata: RedirectMetaName
|
||||
spec: hosts: ["*"]
|
||||
spec: gateways: [RedirectMetaName.name]
|
||||
spec: http: [{
|
||||
match: [{withoutHeaders: ":path": prefix: "/.well-known/acme-challenge/"}]
|
||||
redirect: {
|
||||
scheme: "https"
|
||||
redirectCode: 302
|
||||
VirtualService: {
|
||||
"\(RedirectMetaName.name)": #VirtualService & {
|
||||
metadata: RedirectMetaName
|
||||
spec: hosts: ["*"]
|
||||
spec: gateways: [RedirectMetaName.name]
|
||||
spec: http: [{
|
||||
match: [{withoutHeaders: ":path": prefix: "/.well-known/acme-challenge/"}]
|
||||
redirect: {
|
||||
scheme: "https"
|
||||
redirectCode: 302
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
Deployment: {
|
||||
loopback: #Deployment & {
|
||||
_description: LoopbackDescription
|
||||
metadata: LoopbackMetaName
|
||||
spec: {
|
||||
selector: matchLabels: LoopbackLabels
|
||||
template: {
|
||||
metadata: {
|
||||
annotations: "inject.istio.io/templates": "gateway"
|
||||
annotations: #Description & {
|
||||
_Description: LoopbackDescription
|
||||
}
|
||||
labels: LoopbackLabels & {"sidecar.istio.io/inject": "true"}
|
||||
}
|
||||
spec: {
|
||||
serviceAccountName: "istio-ingressgateway"
|
||||
// Allow binding to all ports (such as 80 and 443)
|
||||
securityContext: {
|
||||
runAsNonRoot: true
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
sysctls: [{name: "net.ipv4.ip_unprivileged_port_start", value: "0"}]
|
||||
}
|
||||
containers: [{
|
||||
name: "istio-proxy"
|
||||
image: "auto" // Managed by istiod
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["ALL"]
|
||||
runAsUser: 1337
|
||||
runAsGroup: 1337
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
Service: {
|
||||
loopback: #Service & {
|
||||
_description: LoopbackDescription
|
||||
metadata: LoopbackMetaName
|
||||
spec: selector: LoopbackLabels
|
||||
spec: ports: [{port: 80, name: "http"}, {port: 443, name: "https"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,52 +158,3 @@ let LoopbackMetaName = {
|
||||
name: LoopbackName
|
||||
namespace: #TargetNamespace
|
||||
}
|
||||
|
||||
// istio-ingressgateway-loopback
|
||||
_APIObjects: {
|
||||
Deployment: {
|
||||
loopback: #Deployment & {
|
||||
_description: LoopbackDescription
|
||||
metadata: LoopbackMetaName
|
||||
spec: {
|
||||
selector: matchLabels: LoopbackLabels
|
||||
template: {
|
||||
metadata: {
|
||||
annotations: "inject.istio.io/templates": "gateway"
|
||||
annotations: #Description & {
|
||||
_Description: LoopbackDescription
|
||||
}
|
||||
labels: LoopbackLabels & {"sidecar.istio.io/inject": "true"}
|
||||
}
|
||||
spec: {
|
||||
serviceAccountName: "istio-ingressgateway"
|
||||
// Allow binding to all ports (such as 80 and 443)
|
||||
securityContext: {
|
||||
runAsNonRoot: true
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
sysctls: [{name: "net.ipv4.ip_unprivileged_port_start", value: "0"}]
|
||||
}
|
||||
containers: [{
|
||||
name: "istio-proxy"
|
||||
image: "auto" // Managed by istiod
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["ALL"]
|
||||
runAsUser: 1337
|
||||
runAsGroup: 1337
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Service: {
|
||||
loopback: #Service & {
|
||||
_description: LoopbackDescription
|
||||
metadata: LoopbackMetaName
|
||||
spec: selector: LoopbackLabels
|
||||
spec: ports: [{port: 80, name: "http"}, {port: 443, name: "https"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package holos
|
||||
|
||||
#DependsOn: _IstioBase
|
||||
|
||||
#HelmChart: {
|
||||
chart: {
|
||||
version: "1.20.3"
|
||||
|
||||
@@ -5,22 +5,28 @@ import "encoding/yaml"
|
||||
#InputKeys: component: "istiod"
|
||||
#TargetNamespace: "istio-system"
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "istiod"
|
||||
}
|
||||
values: #IstioValues & {
|
||||
pilot: {
|
||||
// The istio meshconfig ConfigMap is handled in the holos component instead of
|
||||
// the upstream chart so extension providers can be collected from holos data.
|
||||
configMap: false
|
||||
// Set to `type: RuntimeDefault` to use the default profile if available.
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "\(#InstancePrefix)-istio-base": _
|
||||
|
||||
metadata: name: "prod-mesh-istiod"
|
||||
chart: name: "istiod"
|
||||
namespace: #TargetNamespace
|
||||
_values: #IstioValues & {
|
||||
pilot: {
|
||||
// The istio meshconfig ConfigMap is handled in the holos component instead of
|
||||
// the upstream chart so extension providers can be collected from holos data.
|
||||
configMap: false
|
||||
// Set to `type: RuntimeDefault` to use the default profile if available.
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
}
|
||||
}
|
||||
}
|
||||
apiObjects: ConfigMap: istio: #IstioConfigMap
|
||||
}
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {apiObjects: ConfigMap: istio: #IstioConfigMap}
|
||||
|
||||
#IstioConfigMap: #ConfigMap & {
|
||||
metadata: {
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
package holos
|
||||
|
||||
// Components under this directory are part of this collection
|
||||
#InputKeys: project: "mesh"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#DependsOn: _Namespaces
|
||||
|
||||
// Common Dependencies
|
||||
_CertManager: CertManager: name: "\(#InstancePrefix)-certmanager"
|
||||
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
_IstioBase: IstioBase: name: "\(#InstancePrefix)-istio-base"
|
||||
_IstioD: IstioD: name: "\(#InstancePrefix)-istiod"
|
||||
_IngressGateway: IngressGateway: name: "\(#InstancePrefix)-ingress"
|
||||
#InstancePrefix: "prod-mesh"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package holos
|
||||
|
||||
#DependsOn: Namespaces: name: "prod-secrets-namespaces"
|
||||
#DependsOn: CRDS: name: "\(#InstancePrefix)-crds"
|
||||
#InputKeys: component: "controller"
|
||||
{} & #KustomizeBuild
|
||||
spec: components: KustomizeBuildList: [
|
||||
#KustomizeBuild & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-pgo-crds": _
|
||||
|
||||
metadata: name: "prod-pgo-controller"
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package holos
|
||||
|
||||
// Refer to https://github.com/CrunchyData/postgres-operator-examples/tree/main/kustomize/install/crd
|
||||
|
||||
#InputKeys: component: "crds"
|
||||
{} & #KustomizeBuild
|
||||
spec: components: KustomizeBuildList: [
|
||||
#KustomizeBuild & {
|
||||
metadata: name: "prod-pgo-crds"
|
||||
},
|
||||
]
|
||||
|
||||
@@ -2,8 +2,6 @@ package holos
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
#DependsOn: _ESO
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "eso-creds-refresher"
|
||||
@@ -11,8 +9,17 @@ import "encoding/json"
|
||||
|
||||
#TargetNamespace: #CredsRefresher.namespace
|
||||
|
||||
// output kubernetes api objects for holos
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-secrets-eso": _
|
||||
|
||||
metadata: name: "prod-secrets-eso-creds-refresher"
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
for obj in #CredsRefresherService.objects {
|
||||
let Kind = obj.kind
|
||||
|
||||
@@ -3,26 +3,22 @@ package holos
|
||||
// Manages the External Secrets Operator from the official upstream Helm chart.
|
||||
|
||||
#TargetNamespace: "external-secrets"
|
||||
|
||||
#InputKeys: component: "eso"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
service: "eso"
|
||||
}
|
||||
|
||||
#Kustomization: spec: targetNamespace: #TargetNamespace
|
||||
#DependsOn: Namespaces: name: #InstancePrefix + "-namespaces"
|
||||
|
||||
#HelmChart & {
|
||||
values: installCrds: true
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "external-secrets"
|
||||
version: "0.9.12"
|
||||
repository: {
|
||||
name: "external-secrets"
|
||||
url: "https://charts.external-secrets.io"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
|
||||
metadata: name: "prod-secrets-eso"
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "external-secrets"
|
||||
version: "0.9.12"
|
||||
repository: {
|
||||
name: "external-secrets"
|
||||
url: "https://charts.external-secrets.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_values: installCrds: true
|
||||
},
|
||||
]
|
||||
|
||||
@@ -2,11 +2,3 @@ package holos
|
||||
|
||||
// Components under this directory are part of this collection
|
||||
#InputKeys: project: "secrets"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#DependsOn: _Namespaces
|
||||
|
||||
// Common Dependencies
|
||||
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
_ESO: ESO: name: "\(#InstancePrefix)-eso"
|
||||
_ESOCreds: ESOCreds: name: "\(#InstancePrefix)-eso-creds-refresher"
|
||||
|
||||
@@ -2,27 +2,19 @@ package holos
|
||||
|
||||
import "list"
|
||||
|
||||
#DependsOn: _ESOCreds
|
||||
|
||||
#TargetNamespace: "default"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "stores"
|
||||
}
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-secrets-eso-creds-refresher": _
|
||||
|
||||
// #PlatformNamespaceObjects defines the api objects necessary for eso SecretStores in external clusters to access secrets in a given namespace in the provisioner cluster.
|
||||
#PlatformNamespaceObjects: {
|
||||
_ns: #PlatformNamespace
|
||||
metadata: name: "prod-secrets-stores"
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
objects: [
|
||||
#SecretStore & {
|
||||
_namespace: _ns.name
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#KubernetesObjects & {
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
for ns in #PlatformNamespaces {
|
||||
for obj in (#PlatformNamespaceObjects & {_ns: ns}).objects {
|
||||
@@ -41,3 +33,14 @@ import "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #PlatformNamespaceObjects defines the api objects necessary for eso SecretStores in external clusters to access secrets in a given namespace in the provisioner cluster.
|
||||
#PlatformNamespaceObjects: {
|
||||
_ns: #PlatformNamespace
|
||||
|
||||
objects: [
|
||||
#SecretStore & {
|
||||
_namespace: _ns.name
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ package holos
|
||||
|
||||
#TargetNamespace: "holos-system"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "validate"
|
||||
}
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
|
||||
#DependsOn: _ESO
|
||||
metadata: name: "prod-secrets-validate"
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
#KubernetesObjects & {
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: validate: #ExternalSecret & {
|
||||
_name: "validate"
|
||||
|
||||
@@ -6,29 +6,30 @@ package holos
|
||||
|
||||
#SecretName: "\(#ClusterName)-ceph-csi-rbd"
|
||||
|
||||
#InputKeys: {
|
||||
project: "metal"
|
||||
service: "ceph"
|
||||
component: "ceph"
|
||||
}
|
||||
#Kustomization: spec: targetNamespace: "ceph-system"
|
||||
|
||||
#Kustomization: spec: targetNamespace: #TargetNamespace
|
||||
#DependsOn: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "ceph-csi-rbd"
|
||||
version: "3.10.2"
|
||||
repository: {
|
||||
name: "ceph-csi"
|
||||
url: "https://ceph.github.io/csi-charts"
|
||||
metadata: name: "prod-metal-ceph"
|
||||
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "ceph-csi-rbd"
|
||||
version: "3.10.2"
|
||||
repository: {
|
||||
name: "ceph-csi"
|
||||
url: "https://ceph.github.io/csi-charts"
|
||||
}
|
||||
}
|
||||
}
|
||||
_values: #ChartValues
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "\(#SecretName)": #ExternalSecret & {
|
||||
_name: #SecretName
|
||||
}
|
||||
ExternalSecret: "\(#SecretName)": _
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,46 +5,31 @@ import "encoding/yaml"
|
||||
import "list"
|
||||
|
||||
let Name = "vault"
|
||||
#InputKeys: component: Name
|
||||
#InputKeys: project: "core"
|
||||
#TargetNamespace: "\(#InstancePrefix)-\(Name)"
|
||||
#TargetNamespace: "prod-core-\(Name)"
|
||||
|
||||
let Vault = #OptionalServices[Name]
|
||||
|
||||
if Vault.enabled && list.Contains(Vault.clusterNames, #ClusterName) {
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: Name
|
||||
version: "0.25.0"
|
||||
repository: {
|
||||
name: "hashicorp"
|
||||
url: "https://helm.releases.hashicorp.com"
|
||||
}
|
||||
}
|
||||
values: #Values
|
||||
#Kustomization: spec: wait: true
|
||||
|
||||
apiObjects: {
|
||||
ExternalSecret: "gcpkms-creds": _
|
||||
ExternalSecret: "vault-server-cert": _
|
||||
VirtualService: "\(Name)": {
|
||||
metadata: name: Name
|
||||
metadata: namespace: #TargetNamespace
|
||||
spec: hosts: [for cert in Vault.certs {cert.spec.commonName}]
|
||||
spec: gateways: ["istio-ingress/\(Name)"]
|
||||
spec: http: [
|
||||
{
|
||||
route: [
|
||||
{
|
||||
destination: host: "\(Name)-active"
|
||||
destination: port: number: 8200
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
if Vault.enabled && list.Contains(Vault.clusterNames, #ClusterName) {
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
metadata: name: "prod-core-\(Name)"
|
||||
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: Name
|
||||
version: "0.25.0"
|
||||
repository: {
|
||||
name: "hashicorp"
|
||||
url: "https://helm.releases.hashicorp.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_values: #Values
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
#Kustomize: {
|
||||
patches: [
|
||||
@@ -59,17 +44,40 @@ if Vault.enabled && list.Contains(Vault.clusterNames, #ClusterName) {
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
let EnvPatch = [
|
||||
{
|
||||
op: "test"
|
||||
path: "/spec/template/spec/containers/0/env/4/name"
|
||||
value: "VAULT_ADDR"
|
||||
},
|
||||
{
|
||||
op: "replace"
|
||||
path: "/spec/template/spec/containers/0/env/4/value"
|
||||
value: "http://$(VAULT_K8S_POD_NAME):8200"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
let EnvPatch = [
|
||||
{
|
||||
op: "test"
|
||||
path: "/spec/template/spec/containers/0/env/4/name"
|
||||
value: "VAULT_ADDR"
|
||||
},
|
||||
{
|
||||
op: "replace"
|
||||
path: "/spec/template/spec/containers/0/env/4/value"
|
||||
value: "http://$(VAULT_K8S_POD_NAME):8200"
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "gcpkms-creds": _
|
||||
ExternalSecret: "vault-server-cert": _
|
||||
VirtualService: "\(Name)": {
|
||||
metadata: name: Name
|
||||
metadata: namespace: #TargetNamespace
|
||||
spec: hosts: [for cert in Vault.certs {cert.spec.commonName}]
|
||||
spec: gateways: ["istio-ingress/\(Name)"]
|
||||
spec: http: [
|
||||
{
|
||||
route: [
|
||||
{
|
||||
destination: host: "\(Name)-active"
|
||||
destination: port: number: 8200
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package holos
|
||||
|
||||
#InputKeys: project: "projects"
|
||||
@@ -2,9 +2,3 @@ package holos
|
||||
|
||||
// Components under this directory are part of this collection
|
||||
#InputKeys: project: "iam"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#DependsOn: _Namespaces
|
||||
|
||||
// Common Dependencies
|
||||
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
|
||||
@@ -8,13 +8,27 @@ package holos
|
||||
|
||||
// Refer to [Using Cert Manager to Deploy TLS for Postgres on Kubernetes](https://www.crunchydata.com/blog/using-cert-manager-to-deploy-tls-for-postgres-on-kubernetes)
|
||||
|
||||
#TargetNamespace: "prod-iam-zitadel"
|
||||
#InputKeys: component: "postgres-certs"
|
||||
|
||||
let SelfSigned = "\(_DBName)-selfsigned"
|
||||
let RootCA = "\(_DBName)-root-ca"
|
||||
let DBName = "zitadel"
|
||||
|
||||
let SelfSigned = "\(DBName)-selfsigned"
|
||||
let RootCA = "\(DBName)-root-ca"
|
||||
let Orgs = ["Database"]
|
||||
|
||||
#KubernetesObjects & {
|
||||
#Kustomization: spec: wait: true
|
||||
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-iam-postgres-certs"
|
||||
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
// Put everything in the target namespace.
|
||||
[_]: {
|
||||
@@ -51,10 +65,10 @@ let Orgs = ["Database"]
|
||||
subject: organizations: Orgs
|
||||
}
|
||||
}
|
||||
"\(_DBName)-primary-tls": #DatabaseCert & {
|
||||
"\(DBName)-primary-tls": #DatabaseCert & {
|
||||
// PGO managed name is "<cluster name>-cluster-cert" e.g. zitadel-cluster-cert
|
||||
spec: {
|
||||
commonName: "\(_DBName)-primary"
|
||||
commonName: "\(DBName)-primary"
|
||||
dnsNames: [
|
||||
commonName,
|
||||
"\(commonName).\(#TargetNamespace)",
|
||||
@@ -66,16 +80,16 @@ let Orgs = ["Database"]
|
||||
usages: ["digital signature", "key encipherment"]
|
||||
}
|
||||
}
|
||||
"\(_DBName)-repl-tls": #DatabaseCert & {
|
||||
"\(DBName)-repl-tls": #DatabaseCert & {
|
||||
spec: {
|
||||
commonName: "_crunchyrepl"
|
||||
dnsNames: [commonName]
|
||||
usages: ["digital signature", "key encipherment"]
|
||||
}
|
||||
}
|
||||
"\(_DBName)-client-tls": #DatabaseCert & {
|
||||
"\(DBName)-client-tls": #DatabaseCert & {
|
||||
spec: {
|
||||
commonName: "\(_DBName)-client"
|
||||
commonName: "\(DBName)-client"
|
||||
dnsNames: [commonName]
|
||||
usages: ["digital signature", "key encipherment"]
|
||||
}
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
package holos
|
||||
|
||||
#TargetNamespace: #InstancePrefix + "-zitadel"
|
||||
|
||||
// _DBName is the database name used across multiple holos components in this project
|
||||
_DBName: "zitadel"
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
package holos
|
||||
|
||||
// Provision all platform certificates.
|
||||
#InputKeys: component: "certificates"
|
||||
|
||||
// Certificates usually go into the istio-system namespace, but they may go anywhere.
|
||||
#TargetNamespace: "default"
|
||||
|
||||
// Depends on issuers
|
||||
#DependsOn: _LetsEncrypt
|
||||
#Kustomization: spec: wait: true
|
||||
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "\(#InstancePrefix)-certificates"
|
||||
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-mesh-letsencrypt": _
|
||||
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let Vault = #OptionalServices.vault
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
for k, obj in #PlatformCerts {
|
||||
"\(obj.kind)": {
|
||||
"\(obj.metadata.namespace)/\(obj.metadata.name)": obj
|
||||
}
|
||||
}
|
||||
|
||||
if Vault.enabled {
|
||||
for k, obj in Vault.certs {
|
||||
"\(obj.kind)": "\(obj.metadata.name)": obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package holos
|
||||
|
||||
let Vault = #OptionalServices.vault
|
||||
|
||||
if Vault.enabled {
|
||||
#KubernetesObjects & {
|
||||
apiObjects: {
|
||||
for k, obj in Vault.certs {
|
||||
"\(obj.kind)": "\(obj.metadata.name)": obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,28 +4,28 @@ package holos
|
||||
|
||||
#TargetNamespace: "cert-manager"
|
||||
|
||||
#InputKeys: {
|
||||
component: "certmanager"
|
||||
service: "cert-manager"
|
||||
}
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
metadata: name: "\(#InstancePrefix)-certmanager"
|
||||
|
||||
#HelmChart & {
|
||||
values: #Values & {
|
||||
installCRDs: true
|
||||
startupapicheck: enabled: false
|
||||
// Must not use kube-system on gke autopilot. GKE Warden authz blocks access.
|
||||
global: leaderElection: namespace: #TargetNamespace
|
||||
}
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "cert-manager"
|
||||
version: "1.14.3"
|
||||
repository: {
|
||||
name: "jetstack"
|
||||
url: "https://charts.jetstack.io"
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
namespace: #TargetNamespace
|
||||
_values: #Values & {
|
||||
installCRDs: true
|
||||
startupapicheck: enabled: false
|
||||
// Must not use kube-system on gke autopilot. GKE Warden authz blocks access.
|
||||
global: leaderElection: namespace: #TargetNamespace
|
||||
}
|
||||
}
|
||||
}
|
||||
chart: {
|
||||
name: "cert-manager"
|
||||
version: "1.14.3"
|
||||
repository: {
|
||||
name: "jetstack"
|
||||
url: "https://charts.jetstack.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
// https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests#min-max-requests
|
||||
#PodResources: {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package holos
|
||||
|
||||
// Lets Encrypt certificate issuers for public tls certs
|
||||
#InputKeys: component: "letsencrypt"
|
||||
#TargetNamespace: "cert-manager"
|
||||
|
||||
let Name = "letsencrypt"
|
||||
@@ -9,10 +8,17 @@ let Name = "letsencrypt"
|
||||
// The cloudflare api token is platform scoped, not cluster scoped.
|
||||
#SecretName: "cloudflare-api-token-secret"
|
||||
|
||||
// Depends on cert manager
|
||||
#DependsOn: _CertManager
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "\(#InstancePrefix)-letsencrypt"
|
||||
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "\(#InstancePrefix)-certmanager": _
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ClusterIssuer: {
|
||||
letsencrypt: #ClusterIssuer & {
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
package holos
|
||||
|
||||
// Components under this directory are part of this collection
|
||||
#InputKeys: project: "mesh"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#DependsOn: _Namespaces
|
||||
|
||||
// Common Dependencies
|
||||
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
_CertManager: CertManager: name: "\(#InstancePrefix)-certmanager"
|
||||
_LetsEncrypt: LetsEncrypt: name: "\(#InstancePrefix)-letsencrypt"
|
||||
_Certificates: Certificates: name: "\(#InstancePrefix)-certificates"
|
||||
#InstancePrefix: "prod-mesh"
|
||||
|
||||
@@ -8,10 +8,15 @@ package holos
|
||||
// - Namespace
|
||||
// - ServiceAccount eso-reader, eso-writer
|
||||
|
||||
// No flux kustomization
|
||||
ksObjects: []
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-secrets-eso-creds-refresher"
|
||||
|
||||
#KubernetesObjects & {
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
let role = #CredsRefresherIAM.role
|
||||
let binding = #CredsRefresherIAM.binding
|
||||
@@ -35,12 +40,6 @@ ksObjects: []
|
||||
}
|
||||
}
|
||||
|
||||
#InputKeys: {
|
||||
cluster: "provisioner"
|
||||
project: "secrets"
|
||||
component: "eso-creds-refresher"
|
||||
}
|
||||
|
||||
// #CredsRefresherIAM defines the rbac policy for the job that refreshes credentials used by eso SecretStore resources in clusters other than the provisioner cluster.
|
||||
#CredsRefresherIAM: {
|
||||
let _name = #CredsRefresher.name
|
||||
|
||||
@@ -2,12 +2,15 @@ package holos
|
||||
|
||||
#TargetNamespace: "default"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "namespaces"
|
||||
}
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-secrets-namespaces"
|
||||
|
||||
#KubernetesObjects & {
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
// #ManagedNamespaces is the set of all namespaces across all clusters in the platform.
|
||||
for nsName, ns in #ManagedNamespaces {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package holos
|
||||
|
||||
#TargetNamespace: "external-secrets"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
service: "eso"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package holos
|
||||
|
||||
#InputKeys: component: "namespaces"
|
||||
|
||||
metadata: name: #InstanceName
|
||||
objects: [
|
||||
#Namespace & {
|
||||
metadata: name: #TargetNamespace
|
||||
},
|
||||
]
|
||||
@@ -1,6 +0,0 @@
|
||||
package holos
|
||||
|
||||
// Output schema
|
||||
{} & #KubernetesObjects & {
|
||||
ksObjects: [#Kustomization]
|
||||
}
|
||||
11
docs/examples/project_types.cue
Normal file
11
docs/examples/project_types.cue
Normal file
@@ -0,0 +1,11 @@
|
||||
package holos
|
||||
|
||||
#Project: {
|
||||
name: string
|
||||
}
|
||||
|
||||
#Projects: {
|
||||
[Name=_]: #Project & {
|
||||
name: Name
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
@@ -14,59 +13,26 @@ import (
|
||||
crt "cert-manager.io/certificate/v1"
|
||||
gw "networking.istio.io/gateway/v1beta1"
|
||||
vs "networking.istio.io/virtualservice/v1beta1"
|
||||
kc "sigs.k8s.io/kustomize/api/types"
|
||||
pg "postgres-operator.crunchydata.com/postgrescluster/v1beta1"
|
||||
"encoding/yaml"
|
||||
)
|
||||
|
||||
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 +54,11 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
}
|
||||
|
||||
// Kubernetes API Objects
|
||||
#Namespace: corev1.#Namespace & #ClusterObject & {
|
||||
metadata: {
|
||||
name: string
|
||||
labels: "kubernetes.io/metadata.name": name
|
||||
}
|
||||
#Namespace: corev1.#Namespace & {
|
||||
metadata: name: string
|
||||
metadata: labels: "kubernetes.io/metadata.name": metadata.name
|
||||
}
|
||||
|
||||
#ClusterRole: #ClusterObject & rbacv1.#ClusterRole
|
||||
#ClusterRoleBinding: #ClusterObject & rbacv1.#ClusterRoleBinding
|
||||
#ClusterIssuer: #ClusterObject & ci.#ClusterIssuer & {...}
|
||||
@@ -134,38 +99,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.
|
||||
wait: bool | *true
|
||||
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
|
||||
@@ -315,65 +248,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"
|
||||
}
|
||||
|
||||
// #KubernetesObjectOutput 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
|
||||
@@ -388,57 +262,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
|
||||
|
||||
@@ -451,38 +274,6 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
...
|
||||
}
|
||||
|
||||
// #KustomizeTree represents a kustomize build.
|
||||
#KustomizeFiles: {
|
||||
Objects: {
|
||||
"kustomization.yaml": #Kustomize
|
||||
}
|
||||
// Files holds the marshaled output holos writes to the filesystem
|
||||
Files: {
|
||||
for filename, obj in Objects {
|
||||
"\(filename)": yaml.Marshal(obj)
|
||||
}
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
// kustomization.yaml
|
||||
#Kustomize: kc.#Kustomization & {
|
||||
apiVersion: "kustomize.config.k8s.io/v1beta1"
|
||||
kind: "Kustomization"
|
||||
resources: [ResourcesFile]
|
||||
...
|
||||
if len(#KustomizePatches) > 0 {
|
||||
patches: [for v in #KustomizePatches {v}]
|
||||
}
|
||||
}
|
||||
|
||||
#KustomizePatches: {
|
||||
[_]: #Patch
|
||||
}
|
||||
|
||||
// #Patch is a kustomize patch
|
||||
#Patch: kc.#Patch
|
||||
|
||||
// #DefaultSecurityContext is the holos default security context to comply with the restricted namespace policy.
|
||||
// Refer to https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
|
||||
#DefaultSecurityContext: {
|
||||
@@ -504,8 +295,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
|
||||
|
||||
42
go.mod
42
go.mod
@@ -3,11 +3,12 @@ module github.com/holos-run/holos
|
||||
go 1.21.5
|
||||
|
||||
require (
|
||||
cuelang.org/go v0.7.0
|
||||
cuelang.org/go v0.8.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/rogpeppe/go-internal v1.12.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
golang.org/x/tools v0.18.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/tools v0.19.0
|
||||
k8s.io/api v0.29.2
|
||||
k8s.io/apimachinery v0.29.2
|
||||
k8s.io/client-go v0.29.2
|
||||
@@ -16,50 +17,51 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 // indirect
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e // indirect
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emicklei/proto v1.10.0 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
|
||||
github.com/onsi/gomega v1.31.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/oauth2 v0.10.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/term v0.17.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/oauth2 v0.18.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/term v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.110.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 // indirect
|
||||
k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
)
|
||||
|
||||
112
go.sum
112
go.sum
@@ -1,10 +1,10 @@
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 h1:zkiIe8AxZ/kDjqQN+mDKc5BxoVJOqioSdqApjc+eB1I=
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13/go.mod h1:XGKYSMtsJWfqQYPwq51ZygxAPqpEUj/9bdg16iDPTAA=
|
||||
cuelang.org/go v0.7.0 h1:gMztinxuKfJwMIxtboFsNc6s8AxwJGgsJV+3CuLffHI=
|
||||
cuelang.org/go v0.7.0/go.mod h1:ix+3dM/bSpdG9xg6qpCgnJnpeLtciZu+O/rDbywoMII=
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxwdsEYnlUcJ6PJxOjTeFFCKOh6QWg4oAzQ=
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24=
|
||||
cuelang.org/go v0.8.0 h1:fO1XPe/SUGtc7dhnGnTPbpIDoQm/XxhDtoSF7jzO01c=
|
||||
cuelang.org/go v0.8.0/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -13,24 +13,26 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw=
|
||||
github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
|
||||
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
@@ -44,10 +46,10 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
@@ -65,8 +67,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@@ -78,18 +80,16 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto=
|
||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
|
||||
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
|
||||
github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
|
||||
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -99,8 +99,8 @@ github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0/go.mod h1
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -114,54 +114,65 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
@@ -175,7 +186,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A=
|
||||
@@ -186,12 +196,12 @@ k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg=
|
||||
k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA=
|
||||
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
|
||||
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
|
||||
k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 h1:vzKzxN5uyJZLY8HL1/OovW7BJefnsBIWt8T7Gjh2boQ=
|
||||
k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
|
||||
k8s.io/kubectl v0.29.2 h1:uaDYaBhumvkwz0S2XHt36fK0v5IdNgL7HyUniwb2IUo=
|
||||
k8s.io/kubectl v0.29.2/go.mod h1:BhizuYBGcKaHWyq+G7txGw2fXg576QbPrrnQdQDZgqI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI=
|
||||
k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
|
||||
@@ -4,37 +4,32 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue/build"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"cuelang.org/go/cue/load"
|
||||
"github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
"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 (
|
||||
// Kube is the value of the kind field of holos build output indicating
|
||||
// kubernetes api objects.
|
||||
Kube = "KubernetesObjects"
|
||||
KubernetesObjects = v1alpha1.KubernetesObjectsKind
|
||||
// Helm is the value of the kind field of holos build output indicating helm
|
||||
// values and helm command information.
|
||||
Helm = "HelmChart"
|
||||
Helm = v1alpha1.HelmChartKind
|
||||
// Skip is the value when the instance should be skipped
|
||||
Skip = "Skip"
|
||||
// ChartDir is the chart cache directory name.
|
||||
ChartDir = "vendor"
|
||||
// KustomizeBuild is the value of the kind field of cue output indicating holos should process the component using kustomize build to render output.
|
||||
KustomizeBuild = "KustomizeBuild"
|
||||
KustomizeBuild = v1alpha1.KustomizeBuildKind
|
||||
)
|
||||
|
||||
// An Option configures a Builder
|
||||
@@ -69,181 +64,6 @@ func Cluster(name string) Option {
|
||||
return func(cfg *config) { cfg.cluster = name }
|
||||
}
|
||||
|
||||
type buildInfo struct {
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
}
|
||||
|
||||
// Metadata represents the standard metadata fields of the cue output
|
||||
type Metadata struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// apiObjectMap is the shape of marshalled api objects returned from cue to the
|
||||
// holos cli. A map is used to improve the clarity of error messages from cue.
|
||||
type apiObjectMap map[string]map[string]string
|
||||
|
||||
// fileContentMap is a map of file names to file contents.
|
||||
type fileContentMap map[string]string
|
||||
|
||||
// Result is the build result for display or writing.
|
||||
type Result struct {
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
KsContent string `json:"ksContent,omitempty"`
|
||||
// APIObjectMap holds the marshalled representation of api objects.
|
||||
APIObjectMap apiObjectMap `json:"apiObjectMap,omitempty"`
|
||||
accumulatedOutput string
|
||||
Skip bool
|
||||
// KustomizeFiles holds the files for a kustomize kustomization directory.
|
||||
KustomizeFiles fileContentMap `json:"kustomizeFiles"`
|
||||
// ResourcesFile is the file name used for api objects in kustomization.yaml
|
||||
ResourcesFile string `json:"resourcesFile,omitempty"`
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type Chart struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Release string `json:"release"`
|
||||
Repository Repository `json:"repository"`
|
||||
}
|
||||
|
||||
// A HelmChart represents a helm command to provide chart values in order to render kubernetes api objects.
|
||||
type HelmChart struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Kind string `json:"kind"`
|
||||
Metadata Metadata `json:"metadata"`
|
||||
KsContent string `json:"ksContent"`
|
||||
Namespace string `json:"namespace"`
|
||||
Chart Chart `json:"chart"`
|
||||
ValuesContent string `json:"valuesContent"`
|
||||
EnableHooks bool `json:"enableHooks"`
|
||||
// APIObjectMap holds the marshalled representation of api objects.
|
||||
APIObjectMap apiObjectMap `json:"APIObjectMap"`
|
||||
}
|
||||
|
||||
// Name returns the metadata name of the result. Equivalent to the
|
||||
// OrderedComponent name specified in platform.yaml in the holos prototype.
|
||||
func (r *Result) Name() string {
|
||||
return r.Metadata.Name
|
||||
}
|
||||
|
||||
func (r *Result) Filename(writeTo string, cluster string) string {
|
||||
return filepath.Join(writeTo, "clusters", cluster, "components", r.Name(), r.Name()+".gen.yaml")
|
||||
}
|
||||
|
||||
func (r *Result) KustomizationFilename(writeTo string, cluster string) string {
|
||||
return filepath.Join(writeTo, "clusters", cluster, "holos", "components", r.Name()+"-kustomization.gen.yaml")
|
||||
}
|
||||
|
||||
// AccumulatedOutput returns the accumulated rendered output.
|
||||
func (r *Result) AccumulatedOutput() string {
|
||||
return r.accumulatedOutput
|
||||
}
|
||||
|
||||
// addAPIObjects adds the overlay api objects to accumulatedOutput.
|
||||
func (r *Result) addOverlayObjects(log *slog.Logger) {
|
||||
b := []byte(r.AccumulatedOutput())
|
||||
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)
|
||||
b = util.EnsureNewline(b)
|
||||
header := fmt.Sprintf("---\n# Source: CUE apiObjects.%s.%s\n", kind, name)
|
||||
b = append(b, []byte(header+yamlString)...)
|
||||
b = util.EnsureNewline(b)
|
||||
}
|
||||
}
|
||||
r.accumulatedOutput = string(b)
|
||||
}
|
||||
|
||||
// kustomize replaces the final output with the output of kustomize build if the
|
||||
func (r *Result) kustomize(ctx context.Context) error {
|
||||
log := logger.FromContext(ctx)
|
||||
if r.ResourcesFile == "" {
|
||||
log.DebugContext(ctx, "skipping kustomize: no resourcesFile")
|
||||
return nil
|
||||
}
|
||||
if len(r.KustomizeFiles) < 1 {
|
||||
log.DebugContext(ctx, "skipping kustomize: no kustomizeFiles")
|
||||
return nil
|
||||
}
|
||||
tempDir, err := os.MkdirTemp("", "holos.kustomize")
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
defer remove(ctx, tempDir)
|
||||
|
||||
// Write the main api object resources file for kustomize.
|
||||
target := filepath.Join(tempDir, r.ResourcesFile)
|
||||
b := []byte(r.AccumulatedOutput())
|
||||
b = util.EnsureNewline(b)
|
||||
if err := os.WriteFile(target, b, 0644); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not write resources: %w", err))
|
||||
}
|
||||
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
|
||||
|
||||
// Write the kustomization tree, kustomization.yaml must be in this map for kustomize to work.
|
||||
for file, content := range r.KustomizeFiles {
|
||||
target := filepath.Join(tempDir, file)
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
b := []byte(content)
|
||||
b = util.EnsureNewline(b)
|
||||
if err := os.WriteFile(target, b, 0644); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not write: %w", err))
|
||||
}
|
||||
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
|
||||
}
|
||||
|
||||
// Run kustomize.
|
||||
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", tempDir)
|
||||
if err != nil {
|
||||
log.ErrorContext(ctx, kOut.Stderr.String())
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
// Replace the accumulated output
|
||||
r.accumulatedOutput = kOut.Stdout.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save writes the content to the filesystem for git ops.
|
||||
func (r *Result) Save(ctx context.Context, path string, content string) error {
|
||||
log := logger.FromContext(ctx)
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, os.FileMode(0775)); err != nil {
|
||||
log.WarnContext(ctx, "could not mkdir", "path", dir, "err", err)
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
// Write the kube api objects
|
||||
if err := os.WriteFile(path, []byte(content), os.FileMode(0644)); err != nil {
|
||||
log.WarnContext(ctx, "could not write", "path", path, "err", err)
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
log.DebugContext(ctx, "out: wrote "+path, "action", "write", "path", path, "status", "ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cluster returns the cluster name of the component instance being built.
|
||||
func (b *Builder) Cluster() string {
|
||||
return b.cfg.cluster
|
||||
@@ -285,8 +105,8 @@ func (b *Builder) Instances(ctx context.Context) ([]*build.Instance, error) {
|
||||
return load.Instances(args, &cfg), nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context) (results []*Result, err error) {
|
||||
results = make([]*Result, 0, len(b.cfg.args))
|
||||
func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err error) {
|
||||
results = make([]*v1alpha1.Result, 0, len(b.cfg.args))
|
||||
cueCtx := cuecontext.New()
|
||||
logger.FromContext(ctx).DebugContext(ctx, "cue: building instances")
|
||||
instances, err := b.Instances(ctx)
|
||||
@@ -294,72 +114,67 @@ func (b *Builder) Run(ctx context.Context) (results []*Result, err error) {
|
||||
return results, err
|
||||
}
|
||||
|
||||
// Each CUE instance provides a BuildPlan
|
||||
for _, instance := range instances {
|
||||
var info buildInfo
|
||||
var result Result
|
||||
var buildPlan v1alpha1.BuildPlan
|
||||
|
||||
log := logger.FromContext(ctx).With("dir", instance.Dir)
|
||||
results = append(results, &result)
|
||||
if err := instance.Err; err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not load: %w", err))
|
||||
}
|
||||
log.DebugContext(ctx, "cue: building instance")
|
||||
value := cueCtx.BuildInstance(instance)
|
||||
if err := value.Err(); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not build: %w", err))
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not build %s: %w", instance.Dir, err))
|
||||
}
|
||||
log.DebugContext(ctx, "cue: validating instance")
|
||||
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 {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
|
||||
|
||||
log.DebugContext(ctx, "cue: decoding holos build plan")
|
||||
// Hack to catch unknown fields https://github.com/holos-run/holos/issues/72
|
||||
jsonBytes, err := value.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", instance.Dir, err))
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
decoder.DisallowUnknownFields()
|
||||
err = decoder.Decode(&buildPlan)
|
||||
if err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("invalid BuildPlan: %s: %w", instance.Dir, err))
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
|
||||
if err := buildPlan.Validate(); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not validate %s: %w", instance.Dir, err))
|
||||
}
|
||||
|
||||
if buildPlan.Spec.Disabled {
|
||||
log.DebugContext(ctx, "skipped: spec.disabled is true", "skipped", true)
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: concurrent renders
|
||||
for _, component := range buildPlan.Spec.Components.KubernetesObjectsList {
|
||||
if result, err := component.Render(ctx, holos.PathComponent(instance.Dir)); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
}
|
||||
result.addOverlayObjects(log)
|
||||
case Helm:
|
||||
var helmChart HelmChart
|
||||
// First decode into the result. Helm will populate the api objects later.
|
||||
if err := value.Decode(&result); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
|
||||
}
|
||||
for _, component := range buildPlan.Spec.Components.HelmChartList {
|
||||
if result, err := component.Render(ctx, holos.PathComponent(instance.Dir)); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
}
|
||||
// Decode again into the helm chart struct to get the values content to provide to helm.
|
||||
if err := value.Decode(&helmChart); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
|
||||
}
|
||||
for _, component := range buildPlan.Spec.Components.KustomizeBuildList {
|
||||
if result, err := component.Render(ctx, holos.PathComponent(instance.Dir)); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
}
|
||||
// runHelm populates result.Content from helm template output.
|
||||
if err := runHelm(ctx, &helmChart, &result, holos.PathComponent(instance.Dir)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.addOverlayObjects(log)
|
||||
if err := result.kustomize(ctx); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not kustomize: %w", err))
|
||||
}
|
||||
case KustomizeBuild:
|
||||
// CUE directly provides the kubernetes api objects in result.Content
|
||||
if err := value.Decode(&result); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
|
||||
}
|
||||
// Run kustomize.
|
||||
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", instance.Dir)
|
||||
if err != nil {
|
||||
log.ErrorContext(ctx, kOut.Stderr.String())
|
||||
return nil, wrapper.Wrap(err)
|
||||
}
|
||||
// Replace the accumulated output
|
||||
result.accumulatedOutput = kOut.Stdout.String()
|
||||
result.addOverlayObjects(log)
|
||||
default:
|
||||
return nil, wrapper.Wrap(fmt.Errorf("build kind not implemented: %v", kind))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,119 +210,3 @@ func (b *Builder) findCueMod() (dir holos.PathCueMod, err error) {
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// runHelm provides the values produced by CUE to helm template and returns
|
||||
// the rendered kubernetes api objects in the result.
|
||||
func runHelm(ctx context.Context, hc *HelmChart, r *Result, path holos.PathComponent) error {
|
||||
log := logger.FromContext(ctx).With("chart", hc.Chart.Name)
|
||||
if hc.Chart.Name == "" {
|
||||
log.WarnContext(ctx, "skipping helm: no chart name specified, use a different component type")
|
||||
return nil
|
||||
}
|
||||
|
||||
cachedChartPath := filepath.Join(string(path), ChartDir, filepath.Base(hc.Chart.Name))
|
||||
if isNotExist(cachedChartPath) {
|
||||
// Add repositories
|
||||
repo := hc.Chart.Repository
|
||||
if repo.URL != "" {
|
||||
out, err := util.RunCmd(ctx, "helm", "repo", "add", repo.Name, repo.URL)
|
||||
if err != nil {
|
||||
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
|
||||
return wrapper.Wrap(fmt.Errorf("could not run helm repo add: %w", err))
|
||||
}
|
||||
// Update repository
|
||||
out, err = util.RunCmd(ctx, "helm", "repo", "update", repo.Name)
|
||||
if err != nil {
|
||||
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
|
||||
return wrapper.Wrap(fmt.Errorf("could not run helm repo update: %w", err))
|
||||
}
|
||||
} else {
|
||||
log.DebugContext(ctx, "no chart repository url proceeding assuming oci chart")
|
||||
}
|
||||
|
||||
// Cache the chart
|
||||
if err := cacheChart(ctx, path, ChartDir, hc.Chart); err != nil {
|
||||
return fmt.Errorf("could not cache chart: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Write values file
|
||||
tempDir, err := os.MkdirTemp("", "holos")
|
||||
if err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not make temp dir: %w", err))
|
||||
}
|
||||
defer remove(ctx, tempDir)
|
||||
|
||||
valuesPath := filepath.Join(tempDir, "values.yaml")
|
||||
if err := os.WriteFile(valuesPath, []byte(hc.ValuesContent), 0644); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not write values: %w", err))
|
||||
}
|
||||
log.DebugContext(ctx, "helm: wrote values", "path", valuesPath, "bytes", len(hc.ValuesContent))
|
||||
|
||||
// Run charts
|
||||
chart := hc.Chart
|
||||
args := []string{"template"}
|
||||
if !hc.EnableHooks {
|
||||
args = append(args, "--no-hooks")
|
||||
}
|
||||
args = append(args, "--include-crds", "--values", valuesPath, "--namespace", hc.Namespace, "--kubeconfig", "/dev/null", "--version", chart.Version, chart.Release, cachedChartPath)
|
||||
helmOut, err := util.RunCmd(ctx, "helm", args...)
|
||||
if err != nil {
|
||||
stderr := helmOut.Stderr.String()
|
||||
lines := strings.Split(stderr, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "Error:") {
|
||||
err = fmt.Errorf("%s: %w", line, err)
|
||||
}
|
||||
}
|
||||
return wrapper.Wrap(fmt.Errorf("could not run helm template: %w", err))
|
||||
}
|
||||
|
||||
r.accumulatedOutput = helmOut.Stdout.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// remove cleans up path, useful for temporary directories.
|
||||
func remove(ctx context.Context, path string) {
|
||||
log := logger.FromContext(ctx)
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
log.WarnContext(ctx, "tmp: could not remove", "err", err, "path", path)
|
||||
} else {
|
||||
log.DebugContext(ctx, "tmp: removed", "path", path)
|
||||
}
|
||||
}
|
||||
|
||||
func isNotExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
cacheTemp, err := os.MkdirTemp(string(path), chartDir)
|
||||
if err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not make temp dir: %w", err))
|
||||
}
|
||||
defer remove(ctx, cacheTemp)
|
||||
|
||||
chartName := chart.Name
|
||||
if chart.Repository.Name != "" {
|
||||
chartName = fmt.Sprintf("%s/%s", chart.Repository.Name, chart.Name)
|
||||
}
|
||||
helmOut, err := util.RunCmd(ctx, "helm", "pull", "--destination", cacheTemp, "--untar=true", "--version", chart.Version, chartName)
|
||||
if err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not run helm pull: %w", err))
|
||||
}
|
||||
log.Debug("helm pull", "stdout", helmOut.Stdout, "stderr", helmOut.Stderr)
|
||||
|
||||
cachePath := filepath.Join(string(path), chartDir)
|
||||
if err := os.Rename(cacheTemp, cachePath); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not rename: %w", err))
|
||||
}
|
||||
log.InfoContext(ctx, "cached", "chart", chart.Name, "version", chart.Version, "path", cachePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
17
pkg/util/remove.go
Normal file
17
pkg/util/remove.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Remove cleans up path, useful for temporary directories.
|
||||
func Remove(ctx context.Context, path string) {
|
||||
log := logger.FromContext(ctx)
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
log.WarnContext(ctx, "tmp: could not Remove", "err", err, "path", path)
|
||||
} else {
|
||||
log.DebugContext(ctx, "tmp: removed", "path", path)
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
58
|
||||
60
|
||||
|
||||
@@ -1 +1 @@
|
||||
2
|
||||
3
|
||||
|
||||
Reference in New Issue
Block a user