mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 00:37:45 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53cb9ba7fb | ||
|
|
4cc139b372 | ||
|
|
8bc7804a9c | ||
|
|
a39807a858 | ||
|
|
5170650760 | ||
|
|
1d81b3c3b4 | ||
|
|
33970dafe8 | ||
|
|
faa46c54d8 | ||
|
|
42509a34cf | ||
|
|
ef369d4860 | ||
|
|
747ed3462a | ||
|
|
1fb1798f60 | ||
|
|
accf80200f | ||
|
|
4522ee1d4e | ||
|
|
313ebc6817 | ||
|
|
e0f439515f | ||
|
|
caa7560ab9 | ||
|
|
bbcf280da7 | ||
|
|
6d2daacb7b | ||
|
|
62f96a2d6c | ||
|
|
50f414d520 | ||
|
|
882f3894f3 | ||
|
|
30ddde7b49 | ||
|
|
5cced6fb51 | ||
|
|
a82ebf43b6 | ||
|
|
ebb6d6205a | ||
|
|
58950c469a | ||
|
|
0eebdaf0c7 | ||
|
|
54e2f28f4c | ||
|
|
d4d50ef12b | ||
|
|
075f2b16a4 | ||
|
|
6f8008a53c | ||
|
|
0618b52bae | ||
|
|
f1951c5db3 | ||
|
|
dad12acd8d | ||
|
|
a4503e076f | ||
|
|
09ddd339b8 | ||
|
|
bc94f4b6b8 | ||
|
|
564406f60f | ||
|
|
7845ce62e0 | ||
|
|
a1542752b7 |
8
Dockerfile
Normal file
8
Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM quay.io/holos-run/debian:bullseye AS final
|
||||
USER root
|
||||
WORKDIR /app
|
||||
ADD bin bin
|
||||
RUN chown -R app: /app
|
||||
# Kubernetes requires the user to be numeric
|
||||
USER 8192
|
||||
ENTRYPOINT bin/holos server
|
||||
14
Makefile
14
Makefile
@@ -7,7 +7,7 @@ REPO_PATH=$(ORG_PATH)/$(PROJ)
|
||||
VERSION := $(shell cat version/embedded/major version/embedded/minor version/embedded/patch | xargs printf "%s.%s.%s")
|
||||
BIN_NAME := holos
|
||||
|
||||
DOCKER_REPO=quay.io/openinfrastructure/holos
|
||||
DOCKER_REPO=quay.io/holos-run/holos
|
||||
IMAGE_NAME=$(DOCKER_REPO)
|
||||
|
||||
$( shell mkdir -p bin)
|
||||
@@ -55,6 +55,7 @@ tidy: ## Tidy go module.
|
||||
.PHONY: fmt
|
||||
fmt: ## Format code.
|
||||
cd docs/examples && cue fmt ./...
|
||||
cd internal/generate/platforms && cue fmt ./...
|
||||
go fmt ./...
|
||||
|
||||
.PHONY: vet
|
||||
@@ -64,6 +65,8 @@ vet: ## Vet Go code.
|
||||
.PHONY: gencue
|
||||
gencue: ## Generate CUE definitions
|
||||
cd internal/generate/platforms && cue get go github.com/holos-run/holos/api/v1alpha1/...
|
||||
cd internal/generate/platforms && cue get go github.com/holos-run/holos/api/core/...
|
||||
cd internal/generate/platforms && cue get go github.com/holos-run/holos/api/meta/...
|
||||
|
||||
.PHONY: rmgen
|
||||
rmgen: ## Remove generated code
|
||||
@@ -123,11 +126,13 @@ tools: go-deps frontend-deps ## install tool dependencies
|
||||
|
||||
.PHONY: go-deps
|
||||
go-deps: ## tool versions pinned in tools.go
|
||||
go install cuelang.org/go/cmd/cue
|
||||
go install github.com/bufbuild/buf/cmd/buf
|
||||
go install github.com/fullstorydev/grpcurl/cmd/grpcurl
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go
|
||||
go install connectrpc.com/connect/cmd/protoc-gen-connect-go
|
||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
go install honnef.co/go/tools/cmd/staticcheck
|
||||
go install golang.org/x/tools/cmd/godoc
|
||||
# curl https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash
|
||||
|
||||
.PHONY: frontend-deps
|
||||
@@ -147,6 +152,11 @@ frontend: buf
|
||||
cd internal/frontend/holos && ng build
|
||||
touch internal/frontend/frontend.go
|
||||
|
||||
.PHONY: image
|
||||
image: build ## Docker image build
|
||||
docker build . -t ${DOCKER_REPO}:v$(shell ./bin/holos --version)
|
||||
docker push ${DOCKER_REPO}:v$(shell ./bin/holos --version)
|
||||
|
||||
.PHONY: help
|
||||
help: ## Display this help menu.
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
44
api/core/v1alpha2/apiobjects.go
Normal file
44
api/core/v1alpha2/apiobjects.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package v1alpha2
|
||||
|
||||
import "google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
// Label is an arbitrary unique identifier internal to holos itself. The holos
|
||||
// cli is expected to never write a Label value to rendered output files,
|
||||
// therefore use a [Label] then the identifier must be unique and internal.
|
||||
// Defined as a type for clarity and type checking.
|
||||
//
|
||||
// A Label is useful to convert a CUE struct to a list, for example producing a list of [APIObject] resources from an [APIObjectMap]. A CUE struct using
|
||||
// Label keys is guaranteed to not lose data when rendering output because a
|
||||
// Label is expected to never be written to the final output.
|
||||
type Label string
|
||||
|
||||
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
|
||||
type Kind string
|
||||
|
||||
// APIObject represents the most basic generic form of a single kubernetes api
|
||||
// object. Represented as a JSON object internally for compatibility between
|
||||
// tools, for example loading from CUE.
|
||||
type APIObject structpb.Struct
|
||||
|
||||
// APIObjectMap represents the marshalled yaml representation of kubernetes api
|
||||
// objects. Do not produce an APIObjectMap directly, instead use [APIObjects]
|
||||
// to produce the marshalled yaml representation from CUE data, then provide the
|
||||
// result to [HolosComponent].
|
||||
type APIObjectMap map[Kind]map[Label]string
|
||||
|
||||
// APIObjects represents Kubernetes API objects defined directly from CUE code.
|
||||
// Useful to mix in resources to any kind of [HolosComponent], for example
|
||||
// adding an ExternalSecret resource to a [HelmChart].
|
||||
//
|
||||
// [Kind] must be the resource kind, e.g. Deployment or Service.
|
||||
//
|
||||
// [Label] is an arbitrary internal identifier to uniquely identify the resource
|
||||
// within the context of a `holos` command. Holos will never write the
|
||||
// intermediate label to rendered output.
|
||||
//
|
||||
// Refer to [HolosComponent] which accepts an [APIObjectMap] field provided by
|
||||
// [APIObjects].
|
||||
type APIObjects struct {
|
||||
APIObjects map[Kind]map[Label]APIObject `json:"apiObjects"`
|
||||
APIObjectMap APIObjectMap `json:"apiObjectMap"`
|
||||
}
|
||||
96
api/core/v1alpha2/buildplan.go
Normal file
96
api/core/v1alpha2/buildplan.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package v1alpha2
|
||||
|
||||
// FilePath represents a file path.
|
||||
type FilePath string
|
||||
|
||||
// FileContent represents file contents.
|
||||
type FileContent string
|
||||
|
||||
// FileContentMap represents a mapping of file paths to file contents. Paths
|
||||
// are relative to the `holos` output "deploy" directory, and may contain
|
||||
// sub-directories.
|
||||
type FileContentMap map[FilePath]FileContent
|
||||
|
||||
// BuildPlan represents a build plan for the holos cli to execute. The purpose
|
||||
// of a BuildPlan is to define one or more [HolosComponent] kinds. For example a
|
||||
// [HelmChart], [KustomizeBuild], or [KubernetesObjects].
|
||||
//
|
||||
// A BuildPlan usually has an additional empty [KubernetesObjects] for the
|
||||
// purpose of using the [HolosComponent] DeployFiles field to deploy an ArgoCD
|
||||
// or Flux gitops resource for the holos component.
|
||||
type BuildPlan struct {
|
||||
Kind string `json:"kind" cue:"\"BuildPlan\""`
|
||||
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha2\""`
|
||||
Spec BuildPlanSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// BuildPlanSpec represents the specification of the build plan.
|
||||
type BuildPlanSpec struct {
|
||||
// Disabled causes the holos cli to take no action over the [BuildPlan].
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
// Components represents multiple [HolosComponent] kinds to manage.
|
||||
Components BuildPlanComponents `json:"components,omitempty"`
|
||||
}
|
||||
|
||||
type BuildPlanComponents struct {
|
||||
Resources map[Label]KubernetesObjects `json:"resources,omitempty"`
|
||||
KubernetesObjectsList []KubernetesObjects `json:"kubernetesObjectsList,omitempty"`
|
||||
HelmChartList []HelmChart `json:"helmChartList,omitempty"`
|
||||
KustomizeBuildList []KustomizeBuild `json:"kustomizeBuildList,omitempty"`
|
||||
}
|
||||
|
||||
// HolosComponent defines the fields common to all holos component kinds. Every
|
||||
// holos component kind should embed HolosComponent.
|
||||
type HolosComponent struct {
|
||||
// Kind is a string value representing the resource this object represents.
|
||||
Kind string `json:"kind"`
|
||||
// APIVersion represents the versioned schema of this representation of an object.
|
||||
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha2\""`
|
||||
// Metadata represents data about the holos component such as the Name.
|
||||
Metadata Metadata `json:"metadata"`
|
||||
|
||||
// APIObjectMap holds the marshalled representation of api objects. Useful to
|
||||
// mix in resources to each HolosComponent type, for example adding an
|
||||
// ExternalSecret to a HelmChart HolosComponent. Refer to [APIObjects].
|
||||
APIObjectMap APIObjectMap `json:"apiObjectMap,omitempty"`
|
||||
|
||||
// DeployFiles represents file paths relative to the cluster deploy directory
|
||||
// with the value representing the file content. Intended for defining the
|
||||
// ArgoCD Application resource or Flux Kustomization resource from within CUE,
|
||||
// but may be used to render any file related to the build plan from CUE.
|
||||
DeployFiles FileContentMap `json:"deployFiles,omitempty"`
|
||||
|
||||
// Kustomize represents a kubectl kustomize build post-processing step.
|
||||
Kustomize `json:"kustomize,omitempty"`
|
||||
|
||||
// Skip causes holos to take no action regarding this component.
|
||||
Skip bool `json:"skip" cue:"bool | *false"`
|
||||
}
|
||||
|
||||
// Metadata represents data about the holos component such as the Name.
|
||||
type Metadata struct {
|
||||
// Name represents the name of the holos component.
|
||||
Name string `json:"name"`
|
||||
// Namespace is the primary namespace of the holos component. A holos
|
||||
// component may manage resources in multiple namespaces, in this case
|
||||
// consider setting the component namespace to default.
|
||||
//
|
||||
// This field is optional because not all resources require a namespace,
|
||||
// particularly CRD's and DeployFiles functionality.
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// Kustomize represents resources necessary to execute a kustomize build.
|
||||
// Intended for at least two use cases:
|
||||
//
|
||||
// 1. Process a [KustomizeBuild] [HolosComponent] which represents raw yaml
|
||||
// file resources in a holos component directory.
|
||||
// 2. Post process a [HelmChart] [HolosComponent] to inject istio, patch jobs,
|
||||
// add custom labels, etc...
|
||||
type Kustomize struct {
|
||||
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
|
||||
KustomizeFiles FileContentMap `json:"kustomizeFiles,omitempty"`
|
||||
// ResourcesFile is the file name used for api objects in kustomization.yaml
|
||||
ResourcesFile string `json:"resourcesFile,omitempty"`
|
||||
}
|
||||
11
api/core/v1alpha2/constants.go
Normal file
11
api/core/v1alpha2/constants.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package v1alpha2
|
||||
|
||||
const (
|
||||
APIVersion = "v1alpha2"
|
||||
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"
|
||||
)
|
||||
44
api/core/v1alpha2/core.go
Normal file
44
api/core/v1alpha2/core.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package v1alpha2
|
||||
|
||||
import "google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
type PlatformMetadata struct {
|
||||
// Name represents the Platform name.
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Platform represents a platform to manage. A Platform resource informs holos
|
||||
// which components to build. The platform resource also acts as a container
|
||||
// for the platform model form values provided by the PlatformService. The
|
||||
// primary use case is to collect the cluster names, cluster types, platform
|
||||
// model, and holos components to build into one resource.
|
||||
type Platform struct {
|
||||
// Kind is a string value representing the resource this object represents.
|
||||
Kind string `json:"kind" cue:"\"Platform\""`
|
||||
// APIVersion represents the versioned schema of this representation of an object.
|
||||
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha2\""`
|
||||
// Metadata represents data about the object such as the Name.
|
||||
Metadata PlatformMetadata `json:"metadata"`
|
||||
|
||||
// Spec represents the specification.
|
||||
Spec PlatformSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// PlatformSpec represents the specification of a Platform. Think of a platform
|
||||
// specification as a list of platform components to apply to a list of
|
||||
// kubernetes clusters combined with the user-specified Platform Model.
|
||||
type PlatformSpec struct {
|
||||
// Model represents the platform model holos gets from from the
|
||||
// PlatformService.GetPlatform rpc method and provides to CUE using a tag.
|
||||
Model structpb.Struct `json:"model"`
|
||||
// Components represents a list of holos components to manage.
|
||||
Components []PlatformSpecComponent `json:"components"`
|
||||
}
|
||||
|
||||
// PlatformSpecComponent represents a holos component to build or render.
|
||||
type PlatformSpecComponent struct {
|
||||
// Path is the path of the component relative to the platform root.
|
||||
Path string `json:"path"`
|
||||
// Cluster is the cluster name to provide when rendering the component.
|
||||
Cluster string `json:"cluster"`
|
||||
}
|
||||
24
api/core/v1alpha2/doc.go
Normal file
24
api/core/v1alpha2/doc.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Package v1alpha2 contains the core API contract between the holos cli and CUE
|
||||
// configuration code. Platform designers, operators, and software developers
|
||||
// use this API to write configuration in CUE which `holos` loads. The overall
|
||||
// shape of the API defines imperative actions `holos` should carry out to
|
||||
// render the complete yaml that represents a Platform.
|
||||
//
|
||||
// [Platform] defines the complete configuration of a platform. With the holos
|
||||
// reference platform this takes the shape of one management cluster and at
|
||||
// least two workload cluster. Each cluster has multiple [HolosComponent]
|
||||
// resources applied to it.
|
||||
//
|
||||
// Each holos component path, e.g. `components/namespaces` produces exactly one
|
||||
// [BuildPlan] which in turn contains a set of [HolosComponent] kinds.
|
||||
//
|
||||
// The primary kinds of [HolosComponent] are:
|
||||
//
|
||||
// 1. [HelmChart] to render config from a helm chart.
|
||||
// 2. [KustomizeBuild] to render config from [Kustomize]
|
||||
// 3. [KubernetesObjects] to render [APIObjects] defined directly in CUE
|
||||
// configuration.
|
||||
//
|
||||
// Note that Holos operates as a data pipeline, so the output of a [HelmChart]
|
||||
// may be provided to [Kustomize] for post-processing.
|
||||
package v1alpha2
|
||||
38
api/core/v1alpha2/helm.go
Normal file
38
api/core/v1alpha2/helm.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package v1alpha2
|
||||
|
||||
// HelmChart represents a holos component which wraps around an upstream helm
|
||||
// chart. Holos orchestrates helm by providing values obtained from CUE,
|
||||
// renders the output using `helm template`, then post-processes the helm output
|
||||
// yaml using the general functionality provided by [HolosComponent], for
|
||||
// example [Kustomize] post-rendering and mixing in additional kubernetes api
|
||||
// objects.
|
||||
type HelmChart struct {
|
||||
HolosComponent `json:",inline"`
|
||||
Kind string `json:"kind" cue:"\"HelmChart\""`
|
||||
|
||||
// Chart represents a helm chart to manage.
|
||||
Chart Chart `json:"chart"`
|
||||
// ValuesContent represents the values.yaml file holos passes to the `helm
|
||||
// template` command.
|
||||
ValuesContent string `json:"valuesContent"`
|
||||
// EnableHooks enables helm hooks when executing the `helm template` command.
|
||||
EnableHooks bool `json:"enableHooks" cue:"bool | *false"`
|
||||
}
|
||||
|
||||
// Chart represents a helm chart.
|
||||
type Chart struct {
|
||||
// Name represents the chart name.
|
||||
Name string `json:"name"`
|
||||
// Version represents the chart version.
|
||||
Version string `json:"version"`
|
||||
// Release represents the chart release when executing helm template.
|
||||
Release string `json:"release"`
|
||||
// Repository represents the repository to fetch the chart from.
|
||||
Repository Repository `json:"repository,omitempty"`
|
||||
}
|
||||
|
||||
// Repository represents a helm chart repository.
|
||||
type Repository struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
10
api/core/v1alpha2/kubernetesobjects.go
Normal file
10
api/core/v1alpha2/kubernetesobjects.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package v1alpha2
|
||||
|
||||
const KubernetesObjectsKind = "KubernetesObjects"
|
||||
|
||||
// KubernetesObjects represents a [HolosComponent] composed of Kubernetes API
|
||||
// objects provided directly from CUE using [APIObjects].
|
||||
type KubernetesObjects struct {
|
||||
HolosComponent `json:",inline"`
|
||||
Kind string `json:"kind" cue:"\"KubernetesObjects\""`
|
||||
}
|
||||
8
api/core/v1alpha2/kustomizebuild.go
Normal file
8
api/core/v1alpha2/kustomizebuild.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package v1alpha2
|
||||
|
||||
// KustomizeBuild represents a [HolosComponent] that renders plain yaml files in
|
||||
// the holos component directory using `kubectl kustomize build`.
|
||||
type KustomizeBuild struct {
|
||||
HolosComponent `json:",inline"`
|
||||
Kind string `json:"kind" cue:"\"KustomizeBuild\""`
|
||||
}
|
||||
37
api/meta/v1alpha2/meta.go
Normal file
37
api/meta/v1alpha2/meta.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package v1alpha2
|
||||
|
||||
// TypeMeta describes an individual object in an API response or request with
|
||||
// strings representing the type of the object and its API schema version.
|
||||
// Structures that are versioned or persisted should inline TypeMeta.
|
||||
type TypeMeta struct {
|
||||
// Kind is a string value representing the resource this object represents.
|
||||
Kind string `json:"kind"`
|
||||
// APIVersion defines the versioned schema of this representation of an object.
|
||||
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha2\""`
|
||||
}
|
||||
|
||||
func (tm *TypeMeta) GetKind() string {
|
||||
return tm.Kind
|
||||
}
|
||||
|
||||
func (tm *TypeMeta) GetAPIVersion() string {
|
||||
return tm.APIVersion
|
||||
}
|
||||
|
||||
// Discriminator discriminates the kind of an api object.
|
||||
type Discriminator interface {
|
||||
// GetKind returns Kind.
|
||||
GetKind() string
|
||||
// GetAPIVersion returns APIVersion.
|
||||
GetAPIVersion() string
|
||||
}
|
||||
|
||||
// ObjectMeta represents metadata of a holos component object. The fields are a
|
||||
// copy of upstream kubernetes api machinery but are 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"`
|
||||
// Namespace confines a holos component to a single namespace via kustomize if set.
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
@@ -121,6 +122,14 @@ func (hc *HelmChart) helm(ctx context.Context, r *Result, path holos.InstancePat
|
||||
}
|
||||
|
||||
// cacheChart stores a cached copy of Chart in the chart subdirectory of path.
|
||||
//
|
||||
// It is assumed that the only method responsible for writing to chartDir is
|
||||
// cacheChart itself.
|
||||
//
|
||||
// This relies on the atomicity of moving temporary directories into place on
|
||||
// the same filesystem via os.Rename. If a syscall.EEXIST error occurs during
|
||||
// renaming, it indicates that the cached chart already exists, which is an
|
||||
// expected scenario when this function is called concurrently.
|
||||
func cacheChart(ctx context.Context, path holos.InstancePath, chartDir string, chart Chart) error {
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
@@ -156,11 +165,16 @@ func cacheChart(ctx context.Context, path holos.InstancePath, chartDir string, c
|
||||
dst := filepath.Join(cachePath, item.Name())
|
||||
log.DebugContext(ctx, "rename", "src", src, "dst", dst)
|
||||
if err := os.Rename(src, dst); err != nil {
|
||||
return errors.Wrap(fmt.Errorf("could not rename: %w", err))
|
||||
var linkErr *os.LinkError
|
||||
if errors.As(err, &linkErr) && errors.Is(linkErr.Err, syscall.EEXIST) {
|
||||
log.DebugContext(ctx, "cache already exists", "chart", chart.Name, "chart_version", chart.Version, "path", cachePath)
|
||||
} else {
|
||||
return errors.Wrap(fmt.Errorf("could not rename: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.InfoContext(ctx, "cached", "chart", chart.Name, "version", chart.Version, "path", cachePath)
|
||||
log.InfoContext(ctx, "cached", "chart", chart.Name, "chart_version", chart.Version, "path", cachePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -44,11 +44,6 @@ func (r *Result) KustomizationFilename(writeTo string, cluster string) string {
|
||||
return filepath.Join(writeTo, "clusters", cluster, "holos", "components", r.Metadata.Name+"-kustomization.gen.yaml")
|
||||
}
|
||||
|
||||
// KustomizationContent returns the kustomization file contents to write.
|
||||
func (r *Result) KustomizationContent() string {
|
||||
return r.KsContent
|
||||
}
|
||||
|
||||
// AccumulatedOutput returns the accumulated rendered output.
|
||||
func (r *Result) AccumulatedOutput() string {
|
||||
return r.accumulatedOutput
|
||||
|
||||
@@ -10,7 +10,7 @@ func (tm *TypeMeta) GetKind() string {
|
||||
}
|
||||
|
||||
func (tm *TypeMeta) GetAPIVersion() string {
|
||||
return tm.Kind
|
||||
return tm.APIVersion
|
||||
}
|
||||
|
||||
// Discriminator is an interface to discriminate the kind api object.
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
module: "github.com/holos-run/holos/docs/examples"
|
||||
language: version: "v0.9.2"
|
||||
|
||||
@@ -151,7 +151,7 @@ let OBJECTS = #APIObjects & {
|
||||
loopback: #Service & {
|
||||
_description: LoopbackDescription
|
||||
metadata: LoopbackMetaName
|
||||
spec: selector: LoopbackLabels
|
||||
spec: selector: LoopbackLabels
|
||||
spec: ports: [{port: 80, name: "http"}, {port: 443, name: "https"}]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,14 @@ process. This ensures a namespace scoped `SecretStore` is created to sync
|
||||
3. Render the platform
|
||||
4. Apply the `namespaces` component to the management cluster
|
||||
5. Apply the `eso-creds-manager` component to the management cluster to create the `eso-reader` ksa for the namespace `SecretStore`
|
||||
6. Apply the `namespaces` component to the workload clusters
|
||||
7. On the workload cluster, run the job to fetch the eso-reader creds: `kubectl create job -n holos-system --from=cronjob/eso-creds-refresher eso-creds-refresher-$(date +%s)`
|
||||
8. Apply the secretstores component to the workload cluster.
|
||||
6. Get a timestamp: `STAMP="$(date +%s)"`
|
||||
7. Run the job to populate ecr creds: `kubectl create job -n holos-system --from=cronjob/ecr-creds-manager ecr-creds-manager-$STAMP`
|
||||
8. Wait for the job to complete: `kubectl -n holos-system logs -l job-name=ecr-creds-manager-$STAMP -f`
|
||||
9. Apply the `namespaces` component to the workload clusters
|
||||
10. On the workload cluster, run the job to fetch the eso-reader creds: `kubectl create job -n holos-system --from=cronjob/eso-creds-refresher eso-creds-refresher-${STAMP}`
|
||||
11. Wait for the job to complete: `kubectl -n holos-system logs -l job-name=eso-creds-refresher-${STAMP}`
|
||||
12. Apply the secretstores component to the workload cluster.
|
||||
13. Apply any other cluster specific components which were modified by the `holos render platform ./platform` command.
|
||||
|
||||
Your namespace is created and you have the ability to create secrets in the management cluster and pull them using ExternalSecret resources. (edited)
|
||||
|
||||
|
||||
28
go.mod
28
go.mod
@@ -8,7 +8,7 @@ require (
|
||||
connectrpc.com/grpcreflect v1.2.0
|
||||
connectrpc.com/otelconnect v0.7.0
|
||||
connectrpc.com/validate v0.1.0
|
||||
cuelang.org/go v0.8.0
|
||||
cuelang.org/go v0.9.2
|
||||
entgo.io/ent v0.13.1
|
||||
github.com/bufbuild/buf v1.30.1
|
||||
github.com/choria-io/machine-room v0.0.0-20240417064836-c604da2f005e
|
||||
@@ -29,8 +29,9 @@ require (
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/tools v0.20.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/tools v0.22.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa
|
||||
google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002
|
||||
honnef.co/go/tools v0.4.7
|
||||
@@ -44,9 +45,8 @@ require (
|
||||
|
||||
require (
|
||||
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2 // indirect
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
@@ -215,6 +215,7 @@ require (
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||
github.com/tetratelabs/wazero v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
@@ -225,6 +226,7 @@ require (
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xlab/tablewriter v0.0.0-20160610135559-80b567a11ad5 // indirect
|
||||
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
||||
github.com/yuin/goldmark v1.4.13 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zclconf/go-cty v1.8.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
@@ -236,17 +238,15 @@ require (
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/oauth2 v0.18.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/oauth2 v0.20.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa // indirect
|
||||
google.golang.org/grpc v1.62.1 // indirect
|
||||
|
||||
51
go.sum
51
go.sum
@@ -23,10 +23,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
@@ -46,10 +44,10 @@ connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY
|
||||
connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc=
|
||||
connectrpc.com/validate v0.1.0 h1:r55jirxMK7HO/xZwVHj3w2XkVFarsUM77ZDy367NtH4=
|
||||
connectrpc.com/validate v0.1.0/go.mod h1:GU47c9/x/gd+u9wRSPkrQOP46gx2rMN+Wo37EHgI3Ow=
|
||||
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=
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2 h1:BnG6pr9TTr6CYlrJznYUDj6V7xldD1W+1iXPum0wT/w=
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2/go.mod h1:pK23AUVXuNzzTpfMCA06sxZGeVQ/75FdVtW249de9Uo=
|
||||
cuelang.org/go v0.9.2 h1:pfNiry2PdRBr02G/aKm5k2vhzmqbAOoaB4WurmEbWvs=
|
||||
cuelang.org/go v0.9.2/go.mod h1:qpAYsLOf7gTM1YdEg6cxh553uZ4q9ZDWlPbtZr9q1Wk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=
|
||||
entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A=
|
||||
@@ -638,6 +636,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
|
||||
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
||||
github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g=
|
||||
github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
@@ -670,6 +670,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
@@ -719,8 +720,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -758,8 +759,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -793,16 +794,16 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -868,16 +869,16 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -885,12 +886,12 @@ 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.5/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
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/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -942,8 +943,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
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=
|
||||
@@ -970,8 +971,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// Package v1alpha1 is the unstable version of the core api.
|
||||
package v1alpha1
|
||||
@@ -15,22 +15,25 @@ import (
|
||||
"cuelang.org/go/cue/build"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"cuelang.org/go/cue/load"
|
||||
"github.com/holos-run/holos/api/core/v1alpha2"
|
||||
"github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
"github.com/holos-run/holos/internal/render"
|
||||
)
|
||||
|
||||
const (
|
||||
KubernetesObjects = v1alpha1.KubernetesObjectsKind
|
||||
KubernetesObjects = v1alpha2.KubernetesObjectsKind
|
||||
// Helm is the value of the kind field of holos build output indicating helm
|
||||
// values and helm command information.
|
||||
Helm = v1alpha1.HelmChartKind
|
||||
Helm = v1alpha2.HelmChartKind
|
||||
// Skip is the value when the instance should be skipped
|
||||
Skip = "Skip"
|
||||
// KustomizeBuild is the value of the kind field of cue output indicating holos should process the component using kustomize build to render output.
|
||||
// KustomizeBuild is the value of the kind field of cue output indicating
|
||||
// holos should process the component using kustomize build to render output.
|
||||
KustomizeBuild = v1alpha1.KustomizeBuildKind
|
||||
)
|
||||
|
||||
@@ -46,6 +49,43 @@ type Builder struct {
|
||||
cfg config
|
||||
}
|
||||
|
||||
type buildPlanWrapper struct {
|
||||
buildPlan *v1alpha2.BuildPlan
|
||||
}
|
||||
|
||||
func (b *buildPlanWrapper) validate() error {
|
||||
if b == nil {
|
||||
return fmt.Errorf("invalid BuildPlan: is nil")
|
||||
}
|
||||
bp := b.buildPlan
|
||||
if bp == nil {
|
||||
return fmt.Errorf("invalid BuildPlan: is nil")
|
||||
}
|
||||
errs := make([]string, 0, 2)
|
||||
if bp.Kind != v1alpha2.BuildPlanKind {
|
||||
errs = append(errs, fmt.Sprintf("kind invalid: want: %s have: %s", v1alpha1.BuildPlanKind, bp.Kind))
|
||||
}
|
||||
if bp.APIVersion != v1alpha2.APIVersion {
|
||||
errs = append(errs, fmt.Sprintf("apiVersion invalid: want: %s have: %s", v1alpha2.APIVersion, bp.APIVersion))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("invalid BuildPlan: " + strings.Join(errs, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildPlanWrapper) resultCapacity() (count int) {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
bp := b.buildPlan
|
||||
count = len(bp.Spec.Components.HelmChartList) +
|
||||
len(bp.Spec.Components.KubernetesObjectsList) +
|
||||
len(bp.Spec.Components.KustomizeBuildList) +
|
||||
len(bp.Spec.Components.Resources)
|
||||
return count
|
||||
}
|
||||
|
||||
// New returns a new *Builder configured by opts Option.
|
||||
func New(opts ...Option) *Builder {
|
||||
var cfg config
|
||||
@@ -128,14 +168,14 @@ func (b *Builder) Instances(ctx context.Context, cfg *client.Config) ([]*build.I
|
||||
return load.Instances(args, &cueConfig), nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, cfg *client.Config) (results []*v1alpha1.Result, err error) {
|
||||
func (b *Builder) Run(ctx context.Context, cfg *client.Config) (results []*render.Result, err error) {
|
||||
log := logger.FromContext(ctx)
|
||||
log.DebugContext(ctx, "cue: building instances")
|
||||
instances, err := b.Instances(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = make([]*v1alpha1.Result, 0, len(instances)*8)
|
||||
results = make([]*render.Result, 0, len(instances)*8)
|
||||
|
||||
// Each CUE instance provides a BuildPlan
|
||||
for idx, instance := range instances {
|
||||
@@ -150,7 +190,7 @@ func (b *Builder) Run(ctx context.Context, cfg *client.Config) (results []*v1alp
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (results []*v1alpha1.Result, err error) {
|
||||
func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (results []*render.Result, err error) {
|
||||
path := holos.InstancePath(instance.Dir)
|
||||
log := logger.FromContext(ctx).With("dir", path)
|
||||
|
||||
@@ -191,9 +231,8 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
switch tm.Kind {
|
||||
// TODO(jeff) Process a v1alpha1.Result here, the result is tightly coupled to a BuildPlan.
|
||||
case "BuildPlan":
|
||||
var bp v1alpha1.BuildPlan
|
||||
var bp v1alpha2.BuildPlan
|
||||
if err = decoder.Decode(&bp); err != nil {
|
||||
err = errors.Wrap(fmt.Errorf("could not decode BuildPlan %s: %w", instance.Dir, err))
|
||||
return
|
||||
@@ -209,10 +248,12 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan, path holos.InstancePath) (results []*v1alpha1.Result, err error) {
|
||||
func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha2.BuildPlan, path holos.InstancePath) (results []*render.Result, err error) {
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
if err := buildPlan.Validate(); err != nil {
|
||||
bpw := buildPlanWrapper{buildPlan: buildPlan}
|
||||
|
||||
if err := bpw.validate(); err != nil {
|
||||
log.WarnContext(ctx, "could not validate", "skipped", true, "err", err)
|
||||
return nil, errors.Wrap(fmt.Errorf("could not validate %w", err))
|
||||
}
|
||||
@@ -222,11 +263,12 @@ func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan,
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: concurrent renders
|
||||
results = make([]*v1alpha1.Result, 0, buildPlan.ResultCapacity())
|
||||
log.DebugContext(ctx, "allocated results slice", "cap", buildPlan.ResultCapacity())
|
||||
results = make([]*render.Result, 0, bpw.resultCapacity())
|
||||
log.DebugContext(ctx, "allocated results slice", "cap", bpw.resultCapacity())
|
||||
|
||||
for _, component := range buildPlan.Spec.Components.Resources {
|
||||
if result, err := component.Render(ctx, path); err != nil {
|
||||
ko := render.KubernetesObjects{Component: component}
|
||||
if result, err := ko.Render(ctx, path); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
@@ -234,38 +276,30 @@ func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan,
|
||||
}
|
||||
|
||||
for _, component := range buildPlan.Spec.Components.KubernetesObjectsList {
|
||||
if result, err := component.Render(ctx, path); err != nil {
|
||||
ko := render.KubernetesObjects{Component: component}
|
||||
if result, err := ko.Render(ctx, path); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
for _, component := range buildPlan.Spec.Components.HelmChartList {
|
||||
if result, err := component.Render(ctx, path); err != nil {
|
||||
hc := render.HelmChart{Component: component}
|
||||
if result, err := hc.Render(ctx, path); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
for _, component := range buildPlan.Spec.Components.KustomizeBuildList {
|
||||
if result, err := component.Render(ctx, path); err != nil {
|
||||
kb := render.KustomizeBuild{Component: component}
|
||||
if result, err := kb.Render(ctx, path); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Add a separate Result if there are DeployFiles from the BuildPlan.
|
||||
if len(buildPlan.Spec.DeployFiles) > 0 {
|
||||
results = append(results, &v1alpha1.Result{
|
||||
HolosComponent: v1alpha1.HolosComponent{
|
||||
TypeMeta: buildPlan.TypeMeta,
|
||||
Metadata: buildPlan.Metadata,
|
||||
},
|
||||
DeployFiles: buildPlan.Spec.DeployFiles,
|
||||
})
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "returning results", "len", len(results))
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -9,14 +9,15 @@ import (
|
||||
"cuelang.org/go/cue/build"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/api/v1alpha1"
|
||||
core "github.com/holos-run/holos/api/core/v1alpha2"
|
||||
meta "github.com/holos-run/holos/api/meta/v1alpha2"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
)
|
||||
|
||||
// Platform builds a platform
|
||||
func (b *Builder) Platform(ctx context.Context, cfg *client.Config) (*v1alpha1.Platform, error) {
|
||||
func (b *Builder) Platform(ctx context.Context, cfg *client.Config) (*core.Platform, error) {
|
||||
log := logger.FromContext(ctx)
|
||||
log.DebugContext(ctx, "cue: building platform instance")
|
||||
instances, err := b.Instances(ctx, cfg)
|
||||
@@ -38,7 +39,7 @@ func (b *Builder) Platform(ctx context.Context, cfg *client.Config) (*v1alpha1.P
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (b Builder) runPlatform(ctx context.Context, instance *build.Instance) (*v1alpha1.Platform, error) {
|
||||
func (b Builder) runPlatform(ctx context.Context, instance *build.Instance) (*core.Platform, error) {
|
||||
path := holos.InstancePath(instance.Dir)
|
||||
log := logger.FromContext(ctx).With("dir", path)
|
||||
|
||||
@@ -60,22 +61,23 @@ func (b Builder) runPlatform(ctx context.Context, instance *build.Instance) (*v1
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", instance.Dir, err))
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
// Discriminate the type of build plan.
|
||||
tm := &v1alpha1.TypeMeta{}
|
||||
tm := &meta.TypeMeta{}
|
||||
err = decoder.Decode(tm)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("invalid platform: %s: %w", instance.Dir, err))
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "cue: discriminated build kind: "+tm.Kind, "kind", tm.Kind, "apiVersion", tm.APIVersion)
|
||||
log.DebugContext(ctx, "cue: discriminated build kind: "+tm.GetKind(), "kind", tm.GetKind(), "apiVersion", tm.GetAPIVersion())
|
||||
|
||||
// New decoder for the full object
|
||||
decoder = json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
var pf v1alpha1.Platform
|
||||
switch tm.Kind {
|
||||
var pf core.Platform
|
||||
switch tm.GetKind() {
|
||||
case "Platform":
|
||||
if err = decoder.Decode(&pf); err != nil {
|
||||
err = errors.Wrap(fmt.Errorf("could not decode platform %s: %w", instance.Dir, err))
|
||||
@@ -83,7 +85,7 @@ func (b Builder) runPlatform(ctx context.Context, instance *build.Instance) (*v1
|
||||
}
|
||||
return &pf, nil
|
||||
default:
|
||||
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.Kind))
|
||||
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.GetKind()))
|
||||
}
|
||||
|
||||
return nil, err
|
||||
|
||||
@@ -26,7 +26,7 @@ func makeBuildRunFunc(cfg *client.Config) command.RunFunc {
|
||||
}
|
||||
outs := make([]string, 0, len(results))
|
||||
for idx, result := range results {
|
||||
if result == nil || result.Skip {
|
||||
if result.Continue() {
|
||||
slog.Debug("skip result", "idx", idx, "result", result)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -38,8 +38,9 @@ func NewPlatform(cfg *client.Config) *cobra.Command {
|
||||
}
|
||||
|
||||
func NewPlatformConfig(cfg *client.Config) *cobra.Command {
|
||||
cmd := command.New("config")
|
||||
cmd.Short = "pull platform config"
|
||||
cmd := command.New("model")
|
||||
cmd.Aliases = []string{"config"}
|
||||
cmd.Short = "pull platform model"
|
||||
cmd.Args = cobra.MinimumNArgs(1)
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -35,7 +35,7 @@ func NewPlatform(cfg *client.Config) *cobra.Command {
|
||||
cmd.Args = cobra.NoArgs
|
||||
|
||||
cmd.AddCommand(NewPlatformForm(cfg))
|
||||
// cmd.AddCommand(NewPlatformModel(cfg))
|
||||
cmd.AddCommand(NewPlatformModel(cfg))
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -74,3 +74,34 @@ func NewPlatformForm(cfg *client.Config) *cobra.Command {
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatformModel(cfg *client.Config) *cobra.Command {
|
||||
cmd := command.New("model")
|
||||
cmd.Short = "push platform model to holos server"
|
||||
cmd.Args = cobra.MinimumNArgs(1)
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
if ctx == nil {
|
||||
return errors.Wrap(errors.New("cannot execute: no context"))
|
||||
}
|
||||
ctx = logger.NewContext(ctx, logger.FromContext(ctx).With("server", cfg.Client().Server()))
|
||||
rpc := client.New(cfg)
|
||||
for _, name := range args {
|
||||
// Get the platform config for the platform id.
|
||||
p, err := client.LoadPlatformConfig(ctx, name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// Make the rpc call to update the platform form.
|
||||
if err := rpc.UpdatePlatformModel(ctx, p.PlatformId, p.PlatformModel); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
slog.Default().InfoContext(ctx, fmt.Sprintf("pushed: %s/ui/platform/%s", cfg.Client().Server(), p.PlatformId))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/holos-run/holos/internal/builder"
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
@@ -77,22 +78,13 @@ func NewComponent(cfg *holos.Config) *cobra.Command {
|
||||
if err := result.WriteDeployFiles(ctx, cfg.WriteTo()); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
// Build plans don't have anything but DeployFiles to write.
|
||||
if result.GetKind() == "BuildPlan" {
|
||||
continue
|
||||
}
|
||||
|
||||
// API Objects
|
||||
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
|
||||
if err := result.Save(ctx, path, result.AccumulatedOutput()); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
// Kustomization
|
||||
if result.KustomizationContent() == "" {
|
||||
log.DebugContext(ctx, "flux kustomization: skipped "+result.Name(), "status", "ok", "action", "skipped")
|
||||
if result.SkipWriteAccumulatedOutput() {
|
||||
log.DebugContext(ctx, "skipped writing k8s objects for "+result.Name())
|
||||
} else {
|
||||
path = result.KustomizationFilename(cfg.WriteTo(), cfg.ClusterName())
|
||||
if err := result.Save(ctx, path, result.KustomizationContent()); err != nil {
|
||||
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
|
||||
if err := result.Save(ctx, path, result.AccumulatedOutput()); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
@@ -113,6 +105,9 @@ func NewPlatform(cfg *holos.Config) *cobra.Command {
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
|
||||
var concurrency int
|
||||
cmd.Flags().IntVar(&concurrency, "concurrency", min(runtime.NumCPU(), 8), "Number of concurrent components to render")
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
build := builder.New(builder.Entrypoints(args))
|
||||
@@ -122,7 +117,7 @@ func NewPlatform(cfg *holos.Config) *cobra.Command {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
return render.Platform(ctx, platform, cmd.ErrOrStderr())
|
||||
return render.Platform(ctx, concurrency, platform, cmd.ErrOrStderr())
|
||||
}
|
||||
|
||||
return cmd
|
||||
@@ -135,7 +130,7 @@ type Result interface {
|
||||
KustomizationFilename(writeTo string, cluster string) string
|
||||
Save(ctx context.Context, path string, content string) error
|
||||
AccumulatedOutput() string
|
||||
KustomizationContent() string
|
||||
SkipWriteAccumulatedOutput() bool
|
||||
WriteDeployFiles(ctx context.Context, writeTo string) error
|
||||
GetKind() string
|
||||
GetAPIVersion() string
|
||||
|
||||
@@ -91,6 +91,22 @@ func (c *Client) UpdateForm(ctx context.Context, platformID string, form *object
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) UpdatePlatformModel(ctx context.Context, platformID string, model *structpb.Struct) error {
|
||||
start := time.Now()
|
||||
req := &platform.UpdatePlatformRequest{
|
||||
PlatformId: platformID,
|
||||
Update: &platform.PlatformMutation{Model: model},
|
||||
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"model"}},
|
||||
}
|
||||
_, err := c.pltSvc.UpdatePlatform(ctx, connect.NewRequest(req))
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log := logger.FromContext(ctx)
|
||||
log.DebugContext(ctx, "updated platform", "platform_id", platformID, "duration", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
// PlatformModel gets the platform model from the PlatformService.
|
||||
func (c *Client) PlatformModel(ctx context.Context, platformID string) (*structpb.Struct, error) {
|
||||
start := time.Now()
|
||||
|
||||
86
internal/frontend/holos/package-lock.json
generated
86
internal/frontend/holos/package-lock.json
generated
@@ -18,7 +18,7 @@
|
||||
"@angular/platform-browser": "^17.3.0",
|
||||
"@angular/platform-browser-dynamic": "^17.3.0",
|
||||
"@angular/router": "^17.3.0",
|
||||
"@bufbuild/protobuf": "^1.9.0",
|
||||
"@bufbuild/protobuf": "^1.10.0",
|
||||
"@connectrpc/connect": "^1.4.0",
|
||||
"@connectrpc/connect-query": "^1.4.1",
|
||||
"@connectrpc/connect-web": "^1.4.0",
|
||||
@@ -37,8 +37,8 @@
|
||||
"@angular-eslint/template-parser": "17.3.0",
|
||||
"@angular/cli": "^17.3.4",
|
||||
"@angular/compiler-cli": "^17.3.0",
|
||||
"@bufbuild/buf": "^1.32.1",
|
||||
"@bufbuild/protoc-gen-es": "^1.9.0",
|
||||
"@bufbuild/buf": "^1.34.0",
|
||||
"@bufbuild/protoc-gen-es": "^1.10.0",
|
||||
"@connectrpc/protoc-gen-connect-es": "^1.4.0",
|
||||
"@connectrpc/protoc-gen-connect-query": "^1.4.1",
|
||||
"@ngx-formly/schematics": "^6.3.0",
|
||||
@@ -2494,9 +2494,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf": {
|
||||
"version": "1.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.32.1.tgz",
|
||||
"integrity": "sha512-uPVhqDzYtz9Q7WTodCschf9xXKL5/TQHtU1fKOUmain/dGe66YtSU4LQ0SWmxAQEJIUSmkH4UOPgKEzNMKdWeg==",
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.34.0.tgz",
|
||||
"integrity": "sha512-DR0P746bYiY7ziQTui0bKAvPa7ihCNxONWLtW54HQXvTkGnTc6C1keVaSz4UhNdSsBu/Xsj69GO9SizodfjUtQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@@ -2508,18 +2508,18 @@
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@bufbuild/buf-darwin-arm64": "1.32.1",
|
||||
"@bufbuild/buf-darwin-x64": "1.32.1",
|
||||
"@bufbuild/buf-linux-aarch64": "1.32.1",
|
||||
"@bufbuild/buf-linux-x64": "1.32.1",
|
||||
"@bufbuild/buf-win32-arm64": "1.32.1",
|
||||
"@bufbuild/buf-win32-x64": "1.32.1"
|
||||
"@bufbuild/buf-darwin-arm64": "1.34.0",
|
||||
"@bufbuild/buf-darwin-x64": "1.34.0",
|
||||
"@bufbuild/buf-linux-aarch64": "1.34.0",
|
||||
"@bufbuild/buf-linux-x64": "1.34.0",
|
||||
"@bufbuild/buf-win32-arm64": "1.34.0",
|
||||
"@bufbuild/buf-win32-x64": "1.34.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-darwin-arm64": {
|
||||
"version": "1.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.32.1.tgz",
|
||||
"integrity": "sha512-Duw4StB5sth8s4cQfOa7Be6+OAXfGuuo3ZOkUzJTxWOVH0sWq0nTkO90kXMJOjOkmB/JMnqRQcVAdKuu9u1pcw==",
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.34.0.tgz",
|
||||
"integrity": "sha512-3+h/jSAr7H+KT8MWWRMbN/gQ87KlGLkTGwm4/mpry1ap9Thw/UdOrk5MfmbK3CRM/rlw4mAn1Egu/Q7R5eO98g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2533,9 +2533,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-darwin-x64": {
|
||||
"version": "1.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.32.1.tgz",
|
||||
"integrity": "sha512-3ANVbOoSmfdFxhOvjMDLTr2u35+mdEQcF9Tx39ZEA+Las0WucV6n/bGPwucpH04a9UsW59npNt3IzA4VvUDcyw==",
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.34.0.tgz",
|
||||
"integrity": "sha512-Jdm0COuA2CMKoef2H8rBsRnc16mJUmCQ2KvJH5otvFrMhzPmr1MUyicCybY26HXFD/6DcnbWZvf6W8LfDMMyGQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2549,9 +2549,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-linux-aarch64": {
|
||||
"version": "1.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.32.1.tgz",
|
||||
"integrity": "sha512-QdGirTSFU/WzI/lBo9ph4ThQJS9S8Zm3l/7hg+07GrF57VqB1pUZvnh2298R10/kLKP6lpMtqeVrjMhIcHtxTw==",
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.34.0.tgz",
|
||||
"integrity": "sha512-utSspJlPmVPh4Ugvn9k7MEEMHDZMI13jvwHkBE6wNSkYxxYTRR5zLHtmysaYQo51Fx+3ar6mL4HnhTqLrgO5GA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2565,9 +2565,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-linux-x64": {
|
||||
"version": "1.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.32.1.tgz",
|
||||
"integrity": "sha512-6R8whslj+6WQi9nUjVkNx6AW64czFOFD22dLmrB4i3bY/WDku+/5CNHBU/On738pmgujQrEVT4ztB6fVmVtKOg==",
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.34.0.tgz",
|
||||
"integrity": "sha512-INCGsPLBL4aK2jHBMdZzEJUPv7f6f8skIUMMip7YdJl1nsIh27C/Dl7Q6A6/sv9IhYibWKAoxP7SuiOv2iTdEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2581,9 +2581,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-win32-arm64": {
|
||||
"version": "1.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.32.1.tgz",
|
||||
"integrity": "sha512-QPDxdLRxJpiCTEx7/5bIN3V3EPGvZ1+dyEco6d1qIydDrH9BbCWNy9YLPJOaDxAbewW4lrAX73FmMTTM4tNtbw==",
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.34.0.tgz",
|
||||
"integrity": "sha512-g1EogebjJ93bzmyn/fEi47tTz57M+7WYZ7/vX+DFXgLLYIxTWHDK4YN+3Hs+K7Sbx7KaVdsdEqof8xZ4WoVFnQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2597,9 +2597,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-win32-x64": {
|
||||
"version": "1.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.32.1.tgz",
|
||||
"integrity": "sha512-rZSM5id3zko+YQICZB3ypj+AVL0rcN7gra8SN4Ep4aOWAH6gib3RgH51cFcq9VgI1N1xTBy8wZvQMnMLPBn2zg==",
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.34.0.tgz",
|
||||
"integrity": "sha512-0rPXP7pV7+2twhcpN8hDdgV68UCiazLRcMBjWKubwcSJhAP8jRLqSJv3VGnXmpdYPbYGDQ0htfcgLNUvzllRhQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2613,18 +2613,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.9.0.tgz",
|
||||
"integrity": "sha512-W7gp8Q/v1NlCZLsv8pQ3Y0uCu/SHgXOVFK+eUluUKWXmsb6VHkpNx0apdOWWcDbB9sJoKeP8uPrjmehJz6xETQ=="
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
|
||||
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag=="
|
||||
},
|
||||
"node_modules/@bufbuild/protoc-gen-es": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-1.9.0.tgz",
|
||||
"integrity": "sha512-LJy1nC3Jsfdhs9v48P7qF6YXIqh+usFhXSVzJDTmw0yKjxQ3CKBNISRtaMql/g9hb1MLRU6unHCcFfdz4HSO/Q==",
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-1.10.0.tgz",
|
||||
"integrity": "sha512-zBYBsVT/ul4uZb6F+kD7/k4sWNHVVbEPfJwKi0FDr+9VJo8MKIofI6pkr5ksBLr4fi/74r+e/75Xi/0clL5dXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.9.0",
|
||||
"@bufbuild/protoplugin": "1.9.0"
|
||||
"@bufbuild/protobuf": "^1.10.0",
|
||||
"@bufbuild/protoplugin": "1.10.0"
|
||||
},
|
||||
"bin": {
|
||||
"protoc-gen-es": "bin/protoc-gen-es"
|
||||
@@ -2633,7 +2633,7 @@
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@bufbuild/protobuf": "1.9.0"
|
||||
"@bufbuild/protobuf": "1.10.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@bufbuild/protobuf": {
|
||||
@@ -2642,12 +2642,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protoplugin": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-1.9.0.tgz",
|
||||
"integrity": "sha512-/mxMiGs5h78RUHT7v4+mv0Wt0gyRf/SOS5PLzKEg2sclEAlFPbXfZ8HjlvxJpXZP/YpP3HvsW/mil3E69G0mXg==",
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-1.10.0.tgz",
|
||||
"integrity": "sha512-u6NE4vL0lw1+EK4/PiE/SQB7fKO4LRJNTEScIXVOi2x88K/c8WKc/k0KyEaA0asVBMpwekJQZGnRyj04ZtN5Gg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "1.9.0",
|
||||
"@bufbuild/protobuf": "1.10.0",
|
||||
"@typescript/vfs": "^1.4.0",
|
||||
"typescript": "4.5.2"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"@angular/platform-browser": "^17.3.0",
|
||||
"@angular/platform-browser-dynamic": "^17.3.0",
|
||||
"@angular/router": "^17.3.0",
|
||||
"@bufbuild/protobuf": "^1.9.0",
|
||||
"@bufbuild/protobuf": "^1.10.0",
|
||||
"@connectrpc/connect": "^1.4.0",
|
||||
"@connectrpc/connect-query": "^1.4.1",
|
||||
"@connectrpc/connect-web": "^1.4.0",
|
||||
@@ -40,8 +40,8 @@
|
||||
"@angular-eslint/template-parser": "17.3.0",
|
||||
"@angular/cli": "^17.3.4",
|
||||
"@angular/compiler-cli": "^17.3.0",
|
||||
"@bufbuild/buf": "^1.32.1",
|
||||
"@bufbuild/protoc-gen-es": "^1.9.0",
|
||||
"@bufbuild/buf": "^1.34.0",
|
||||
"@bufbuild/protoc-gen-es": "^1.10.0",
|
||||
"@connectrpc/protoc-gen-connect-es": "^1.4.0",
|
||||
"@connectrpc/protoc-gen-connect-query": "^1.4.1",
|
||||
"@ngx-formly/schematics": "^6.3.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/object/v1alpha1/object.proto (package holos.object.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/organization/v1alpha1/organization.proto (package holos.organization.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/organization/v1alpha1/organization_service.proto (package holos.organization.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/platform/v1alpha1/platform.proto (package holos.platform.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/platform/v1alpha1/platform_service.proto (package holos.platform.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/storage/v1alpha1/storage.proto (package holos.storage.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/system/v1alpha1/system.proto (package holos.system.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/system/v1alpha1/system_service.proto (package holos.system.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/user/v1alpha1/user.proto (package holos.user.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/user/v1alpha1/user_service.proto (package holos.user.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
|
||||
import v1 "github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
// #Helm represents a holos build plan composed of one or more helm charts.
|
||||
#Helm: {
|
||||
Name: string
|
||||
Version: string
|
||||
Namespace: string
|
||||
Name: string
|
||||
Version: string
|
||||
Namespace: string
|
||||
|
||||
Repo: {
|
||||
name: string | *""
|
||||
url: string | *""
|
||||
}
|
||||
Repo: {
|
||||
name: string | *""
|
||||
url: string | *""
|
||||
}
|
||||
|
||||
Values: {...}
|
||||
Values: {...}
|
||||
|
||||
Chart: v1.#HelmChart & {
|
||||
metadata: name: string | *Name
|
||||
namespace: string | *Namespace
|
||||
chart: name: string | *Name
|
||||
chart: version: string | *Version
|
||||
chart: repository: Repo
|
||||
// Render the values to yaml for holos to provide to helm.
|
||||
valuesContent: yaml.Marshal(Values)
|
||||
}
|
||||
Chart: v1.#HelmChart & {
|
||||
metadata: name: string | *Name
|
||||
namespace: string | *Namespace
|
||||
chart: name: string | *Name
|
||||
chart: version: string | *Version
|
||||
chart: repository: Repo
|
||||
// Render the values to yaml for holos to provide to helm.
|
||||
valuesContent: yaml.Marshal(Values)
|
||||
}
|
||||
|
||||
// output represents the build plan provided to the holos cli.
|
||||
// output represents the build plan provided to the holos cli.
|
||||
Output: v1.#BuildPlan & {
|
||||
spec: components: helmChartList: [Chart]
|
||||
}
|
||||
spec: components: helmChartList: [Chart]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,13 +58,13 @@ let FormBuilder = v1.#FormBuilder & {
|
||||
multiple: true
|
||||
selectAllOption: "Select All"
|
||||
options: [
|
||||
{value: "aws", label: "Amazon Web Services"},
|
||||
{value: "gcp", label: "Google Cloud Platform"},
|
||||
{value: "azure", label: "Microsoft Azure"},
|
||||
{value: "aws", label: "Amazon Web Services"},
|
||||
{value: "gcp", label: "Google Cloud Platform"},
|
||||
{value: "azure", label: "Microsoft Azure"},
|
||||
{value: "cloudflare", label: "Cloudflare"},
|
||||
{value: "github", label: "GitHub"},
|
||||
{value: "ois", label: "Open Infrastructure Services"},
|
||||
{value: "onprem", label: "On Premises", disabled: true},
|
||||
{value: "github", label: "GitHub"},
|
||||
{value: "ois", label: "Open Infrastructure Services"},
|
||||
{value: "onprem", label: "On Premises", disabled: true},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -237,73 +237,73 @@ let FormBuilder = v1.#FormBuilder & {
|
||||
}
|
||||
|
||||
let GCPRegions = [
|
||||
{value: "africa-south1", label: "africa-south1"},
|
||||
{value: "asia-east1", label: "asia-east1"},
|
||||
{value: "asia-east2", label: "asia-east2"},
|
||||
{value: "asia-northeast1", label: "asia-northeast1"},
|
||||
{value: "asia-northeast2", label: "asia-northeast2"},
|
||||
{value: "asia-northeast3", label: "asia-northeast3"},
|
||||
{value: "asia-south1", label: "asia-south1"},
|
||||
{value: "asia-south2", label: "asia-south2"},
|
||||
{value: "asia-southeast1", label: "asia-southeast1"},
|
||||
{value: "asia-southeast2", label: "asia-southeast2"},
|
||||
{value: "australia-southeast1", label: "australia-southeast1"},
|
||||
{value: "australia-southeast2", label: "australia-southeast2"},
|
||||
{value: "europe-central2", label: "europe-central2"},
|
||||
{value: "europe-north1", label: "europe-north1"},
|
||||
{value: "europe-southwest1", label: "europe-southwest1"},
|
||||
{value: "europe-west1", label: "europe-west1"},
|
||||
{value: "europe-west10", label: "europe-west10"},
|
||||
{value: "europe-west12", label: "europe-west12"},
|
||||
{value: "europe-west2", label: "europe-west2"},
|
||||
{value: "europe-west3", label: "europe-west3"},
|
||||
{value: "europe-west4", label: "europe-west4"},
|
||||
{value: "europe-west6", label: "europe-west6"},
|
||||
{value: "europe-west8", label: "europe-west8"},
|
||||
{value: "europe-west9", label: "europe-west9"},
|
||||
{value: "me-central1", label: "me-central1"},
|
||||
{value: "me-central2", label: "me-central2"},
|
||||
{value: "me-west1", label: "me-west1"},
|
||||
{value: "africa-south1", label: "africa-south1"},
|
||||
{value: "asia-east1", label: "asia-east1"},
|
||||
{value: "asia-east2", label: "asia-east2"},
|
||||
{value: "asia-northeast1", label: "asia-northeast1"},
|
||||
{value: "asia-northeast2", label: "asia-northeast2"},
|
||||
{value: "asia-northeast3", label: "asia-northeast3"},
|
||||
{value: "asia-south1", label: "asia-south1"},
|
||||
{value: "asia-south2", label: "asia-south2"},
|
||||
{value: "asia-southeast1", label: "asia-southeast1"},
|
||||
{value: "asia-southeast2", label: "asia-southeast2"},
|
||||
{value: "australia-southeast1", label: "australia-southeast1"},
|
||||
{value: "australia-southeast2", label: "australia-southeast2"},
|
||||
{value: "europe-central2", label: "europe-central2"},
|
||||
{value: "europe-north1", label: "europe-north1"},
|
||||
{value: "europe-southwest1", label: "europe-southwest1"},
|
||||
{value: "europe-west1", label: "europe-west1"},
|
||||
{value: "europe-west10", label: "europe-west10"},
|
||||
{value: "europe-west12", label: "europe-west12"},
|
||||
{value: "europe-west2", label: "europe-west2"},
|
||||
{value: "europe-west3", label: "europe-west3"},
|
||||
{value: "europe-west4", label: "europe-west4"},
|
||||
{value: "europe-west6", label: "europe-west6"},
|
||||
{value: "europe-west8", label: "europe-west8"},
|
||||
{value: "europe-west9", label: "europe-west9"},
|
||||
{value: "me-central1", label: "me-central1"},
|
||||
{value: "me-central2", label: "me-central2"},
|
||||
{value: "me-west1", label: "me-west1"},
|
||||
{value: "northamerica-northeast1", label: "northamerica-northeast1"},
|
||||
{value: "northamerica-northeast2", label: "northamerica-northeast2"},
|
||||
{value: "southamerica-east1", label: "southamerica-east1"},
|
||||
{value: "southamerica-west1", label: "southamerica-west1"},
|
||||
{value: "us-central1", label: "us-central1"},
|
||||
{value: "us-east1", label: "us-east1"},
|
||||
{value: "us-east4", label: "us-east4"},
|
||||
{value: "us-east5", label: "us-east5"},
|
||||
{value: "us-south1", label: "us-south1"},
|
||||
{value: "us-west1", label: "us-west1"},
|
||||
{value: "us-west2", label: "us-west2"},
|
||||
{value: "us-west3", label: "us-west3"},
|
||||
{value: "us-west4", label: "us-west4"},
|
||||
{value: "southamerica-east1", label: "southamerica-east1"},
|
||||
{value: "southamerica-west1", label: "southamerica-west1"},
|
||||
{value: "us-central1", label: "us-central1"},
|
||||
{value: "us-east1", label: "us-east1"},
|
||||
{value: "us-east4", label: "us-east4"},
|
||||
{value: "us-east5", label: "us-east5"},
|
||||
{value: "us-south1", label: "us-south1"},
|
||||
{value: "us-west1", label: "us-west1"},
|
||||
{value: "us-west2", label: "us-west2"},
|
||||
{value: "us-west3", label: "us-west3"},
|
||||
{value: "us-west4", label: "us-west4"},
|
||||
]
|
||||
|
||||
let AWSRegions = [
|
||||
{value: "us-east-1", label: "N. Virginia (us-east-1)"},
|
||||
{value: "us-east-2", label: "Ohio (us-east-2)"},
|
||||
{value: "us-west-1", label: "N. California (us-west-1)"},
|
||||
{value: "us-west-2", label: "Oregon (us-west-2)"},
|
||||
{value: "us-gov-west1", label: "US GovCloud West (us-gov-west1)"},
|
||||
{value: "us-gov-east1", label: "US GovCloud East (us-gov-east1)"},
|
||||
{value: "ca-central-1", label: "Canada (ca-central-1)"},
|
||||
{value: "eu-north-1", label: "Stockholm (eu-north-1)"},
|
||||
{value: "eu-west-1", label: "Ireland (eu-west-1)"},
|
||||
{value: "eu-west-2", label: "London (eu-west-2)"},
|
||||
{value: "eu-west-3", label: "Paris (eu-west-3)"},
|
||||
{value: "eu-central-1", label: "Frankfurt (eu-central-1)"},
|
||||
{value: "eu-south-1", label: "Milan (eu-south-1)"},
|
||||
{value: "af-south-1", label: "Cape Town (af-south-1)"},
|
||||
{value: "us-east-1", label: "N. Virginia (us-east-1)"},
|
||||
{value: "us-east-2", label: "Ohio (us-east-2)"},
|
||||
{value: "us-west-1", label: "N. California (us-west-1)"},
|
||||
{value: "us-west-2", label: "Oregon (us-west-2)"},
|
||||
{value: "us-gov-west1", label: "US GovCloud West (us-gov-west1)"},
|
||||
{value: "us-gov-east1", label: "US GovCloud East (us-gov-east1)"},
|
||||
{value: "ca-central-1", label: "Canada (ca-central-1)"},
|
||||
{value: "eu-north-1", label: "Stockholm (eu-north-1)"},
|
||||
{value: "eu-west-1", label: "Ireland (eu-west-1)"},
|
||||
{value: "eu-west-2", label: "London (eu-west-2)"},
|
||||
{value: "eu-west-3", label: "Paris (eu-west-3)"},
|
||||
{value: "eu-central-1", label: "Frankfurt (eu-central-1)"},
|
||||
{value: "eu-south-1", label: "Milan (eu-south-1)"},
|
||||
{value: "af-south-1", label: "Cape Town (af-south-1)"},
|
||||
{value: "ap-northeast-1", label: "Tokyo (ap-northeast-1)"},
|
||||
{value: "ap-northeast-2", label: "Seoul (ap-northeast-2)"},
|
||||
{value: "ap-northeast-3", label: "Osaka (ap-northeast-3)"},
|
||||
{value: "ap-southeast-1", label: "Singapore (ap-southeast-1)"},
|
||||
{value: "ap-southeast-2", label: "Sydney (ap-southeast-2)"},
|
||||
{value: "ap-east-1", label: "Hong Kong (ap-east-1)"},
|
||||
{value: "ap-south-1", label: "Mumbai (ap-south-1)"},
|
||||
{value: "me-south-1", label: "Bahrain (me-south-1)"},
|
||||
{value: "sa-east-1", label: "São Paulo (sa-east-1)"},
|
||||
{value: "cn-north-1", label: "Bejing (cn-north-1)"},
|
||||
{value: "ap-east-1", label: "Hong Kong (ap-east-1)"},
|
||||
{value: "ap-south-1", label: "Mumbai (ap-south-1)"},
|
||||
{value: "me-south-1", label: "Bahrain (me-south-1)"},
|
||||
{value: "sa-east-1", label: "São Paulo (sa-east-1)"},
|
||||
{value: "cn-north-1", label: "Bejing (cn-north-1)"},
|
||||
{value: "cn-northwest-1", label: "Ningxia (cn-northwest-1)"},
|
||||
{value: "ap-southeast-3", label: "Jakarta (ap-southeast-3)"},
|
||||
]
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
// Code generated by timoni. DO NOT EDIT.
|
||||
|
||||
//timoni:generate timoni vendor crd -f https://raw.githubusercontent.com/crossplane-contrib/provider-upjet-aws/v1.5.0/package/crds/aws.upbound.io_providerconfigs.yaml
|
||||
|
||||
package v1beta1
|
||||
|
||||
import "strings"
|
||||
|
||||
// A ProviderConfig configures the AWS provider.
|
||||
#ProviderConfig: {
|
||||
// APIVersion defines the versioned schema of this representation
|
||||
// of an object.
|
||||
// Servers should convert recognized schemas to the latest
|
||||
// internal value, and
|
||||
// may reject unrecognized values.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
apiVersion: "aws.upbound.io/v1beta1"
|
||||
|
||||
// Kind is a string value representing the REST resource this
|
||||
// object represents.
|
||||
// Servers may infer this from the endpoint the client submits
|
||||
// requests to.
|
||||
// Cannot be updated.
|
||||
// In CamelCase.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
kind: "ProviderConfig"
|
||||
metadata!: {
|
||||
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
namespace?: strings.MaxRunes(63) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
labels?: {
|
||||
[string]: string
|
||||
}
|
||||
annotations?: {
|
||||
[string]: string
|
||||
}
|
||||
}
|
||||
|
||||
// A ProviderConfigSpec defines the desired state of a
|
||||
// ProviderConfig.
|
||||
spec!: #ProviderConfigSpec
|
||||
}
|
||||
|
||||
// A ProviderConfigSpec defines the desired state of a
|
||||
// ProviderConfig.
|
||||
#ProviderConfigSpec: {
|
||||
// AssumeRoleChain defines the options for assuming an IAM role
|
||||
assumeRoleChain?: [...{
|
||||
// ExternalID is the external ID used when assuming role.
|
||||
externalID?: string
|
||||
|
||||
// AssumeRoleARN to assume with provider credentials
|
||||
roleARN?: string
|
||||
|
||||
// Tags is list of session tags that you want to pass. Each
|
||||
// session tag consists of a key
|
||||
// name and an associated value. For more information about
|
||||
// session tags, see
|
||||
// Tagging STS Sessions
|
||||
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html).
|
||||
tags?: [...{
|
||||
// Name of the tag.
|
||||
// Key is a required field
|
||||
key: string
|
||||
|
||||
// Value of the tag.
|
||||
// Value is a required field
|
||||
value: string
|
||||
}]
|
||||
|
||||
// TransitiveTagKeys is a list of keys for session tags that you
|
||||
// want to set as transitive. If you set a
|
||||
// tag key as transitive, the corresponding key and value passes
|
||||
// to subsequent
|
||||
// sessions in a role chain. For more information, see Chaining
|
||||
// Roles with Session Tags
|
||||
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining).
|
||||
transitiveTagKeys?: [...string]
|
||||
}]
|
||||
|
||||
// Credentials required to authenticate to this provider.
|
||||
credentials: {
|
||||
env?: {
|
||||
// Name is the name of an environment variable.
|
||||
name: string
|
||||
}
|
||||
fs?: {
|
||||
// Path is a filesystem path.
|
||||
path: string
|
||||
}
|
||||
|
||||
// A SecretRef is a reference to a secret key that contains the
|
||||
// credentials
|
||||
// that must be used to connect to the provider.
|
||||
secretRef?: {
|
||||
// The key to select.
|
||||
key: string
|
||||
|
||||
// Name of the secret.
|
||||
name: string
|
||||
|
||||
// Namespace of the secret.
|
||||
namespace: string
|
||||
}
|
||||
|
||||
// Source of the provider credentials.
|
||||
source: "None" | "Secret" | "IRSA" | "WebIdentity" | "Upbound"
|
||||
upbound?: {
|
||||
// WebIdentity defines the options for assuming an IAM role with a
|
||||
// Web
|
||||
// Identity.
|
||||
webIdentity?: {
|
||||
// AssumeRoleARN to assume with provider credentials
|
||||
roleARN?: string
|
||||
|
||||
// RoleSessionName is the session name, if you wish to uniquely
|
||||
// identify this session.
|
||||
roleSessionName?: string
|
||||
|
||||
// TokenConfig is the Web Identity Token config to assume the
|
||||
// role.
|
||||
tokenConfig?: {
|
||||
fs?: {
|
||||
// Path is a filesystem path.
|
||||
path: string
|
||||
}
|
||||
|
||||
// A SecretRef is a reference to a secret key that contains the
|
||||
// credentials
|
||||
// that must be used to obtain the web identity token.
|
||||
secretRef?: {
|
||||
// The key to select.
|
||||
key: string
|
||||
|
||||
// Name of the secret.
|
||||
name: string
|
||||
|
||||
// Namespace of the secret.
|
||||
namespace: string
|
||||
}
|
||||
|
||||
// Source is the source of the web identity token.
|
||||
source: "Secret" | "Filesystem"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WebIdentity defines the options for assuming an IAM role with a
|
||||
// Web Identity.
|
||||
webIdentity?: {
|
||||
// AssumeRoleARN to assume with provider credentials
|
||||
roleARN?: string
|
||||
|
||||
// RoleSessionName is the session name, if you wish to uniquely
|
||||
// identify this session.
|
||||
roleSessionName?: string
|
||||
|
||||
// TokenConfig is the Web Identity Token config to assume the
|
||||
// role.
|
||||
tokenConfig?: {
|
||||
fs?: {
|
||||
// Path is a filesystem path.
|
||||
path: string
|
||||
}
|
||||
|
||||
// A SecretRef is a reference to a secret key that contains the
|
||||
// credentials
|
||||
// that must be used to obtain the web identity token.
|
||||
secretRef?: {
|
||||
// The key to select.
|
||||
key: string
|
||||
|
||||
// Name of the secret.
|
||||
name: string
|
||||
|
||||
// Namespace of the secret.
|
||||
namespace: string
|
||||
}
|
||||
|
||||
// Source is the source of the web identity token.
|
||||
source: "Secret" | "Filesystem"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Endpoint is where you can override the default endpoint
|
||||
// configuration
|
||||
// of AWS calls made by the provider.
|
||||
endpoint?: {
|
||||
// Specifies if the endpoint's hostname can be modified by the
|
||||
// SDK's API
|
||||
// client.
|
||||
//
|
||||
//
|
||||
// If the hostname is mutable the SDK API clients may modify any
|
||||
// part of
|
||||
// the hostname based on the requirements of the API, (e.g.
|
||||
// adding, or
|
||||
// removing content in the hostname). Such as, Amazon S3 API
|
||||
// client
|
||||
// prefixing "bucketname" to the hostname, or changing the
|
||||
// hostname service name component from "s3." to
|
||||
// "s3-accesspoint.dualstack."
|
||||
// for the dualstack endpoint of an S3 Accesspoint resource.
|
||||
//
|
||||
//
|
||||
// Care should be taken when providing a custom endpoint for an
|
||||
// API. If the
|
||||
// endpoint hostname is mutable, and the client cannot modify the
|
||||
// endpoint
|
||||
// correctly, the operation call will most likely fail, or have
|
||||
// undefined
|
||||
// behavior.
|
||||
//
|
||||
//
|
||||
// If hostname is immutable, the SDK API clients will not modify
|
||||
// the
|
||||
// hostname of the URL. This may cause the API client not to
|
||||
// function
|
||||
// correctly if the API requires the operation specific hostname
|
||||
// values
|
||||
// to be used by the client.
|
||||
//
|
||||
//
|
||||
// This flag does not modify the API client's behavior if this
|
||||
// endpoint
|
||||
// will be used instead of Endpoint Discovery, or if the endpoint
|
||||
// will be
|
||||
// used to perform Endpoint Discovery. That behavior is configured
|
||||
// via the
|
||||
// API Client's Options.
|
||||
// Note that this is effective only for resources that use AWS SDK
|
||||
// v2.
|
||||
hostnameImmutable?: bool
|
||||
|
||||
// The AWS partition the endpoint belongs to.
|
||||
partitionId?: string
|
||||
|
||||
// Specifies the list of services you want endpoint to be used for
|
||||
services?: [...string]
|
||||
|
||||
// The signing method that should be used for signing the requests
|
||||
// to the
|
||||
// endpoint.
|
||||
signingMethod?: string
|
||||
|
||||
// The service name that should be used for signing the requests
|
||||
// to the
|
||||
// endpoint.
|
||||
signingName?: string
|
||||
|
||||
// The region that should be used for signing the request to the
|
||||
// endpoint.
|
||||
// For IAM, which doesn't have any region, us-east-1 is used to
|
||||
// sign the
|
||||
// requests, which is the only signing region of IAM.
|
||||
signingRegion?: string
|
||||
|
||||
// The source of the Endpoint. By default, this will be
|
||||
// ServiceMetadata.
|
||||
// When providing a custom endpoint, you should set the source as
|
||||
// Custom.
|
||||
// If source is not provided when providing a custom endpoint, the
|
||||
// SDK may not
|
||||
// perform required host mutations correctly. Source should be
|
||||
// used along with
|
||||
// HostnameImmutable property as per the usage requirement.
|
||||
// Note that this is effective only for resources that use AWS SDK
|
||||
// v2.
|
||||
source?: "ServiceMetadata" | "Custom"
|
||||
|
||||
// URL lets you configure the endpoint URL to be used in SDK
|
||||
// calls.
|
||||
url: {
|
||||
// Dynamic lets you configure the behavior of endpoint URL
|
||||
// resolver.
|
||||
dynamic?: {
|
||||
// Host is the address of the main host that the resolver will use
|
||||
// to
|
||||
// prepend protocol, service and region configurations.
|
||||
// For example, the final URL for EC2 in us-east-1 looks like
|
||||
// https://ec2.us-east-1.amazonaws.com
|
||||
// You would need to use "amazonaws.com" as Host and "https" as
|
||||
// protocol
|
||||
// to have the resolver construct it.
|
||||
host: string
|
||||
|
||||
// Protocol is the HTTP protocol that will be used in the URL.
|
||||
// Currently,
|
||||
// only http and https are supported.
|
||||
protocol: "http" | "https"
|
||||
}
|
||||
|
||||
// Static is the full URL you'd like the AWS SDK to use.
|
||||
// Recommended for using tools like localstack where a single host
|
||||
// is exposed
|
||||
// for all services and regions.
|
||||
static?: string
|
||||
|
||||
// You can provide a static URL that will be used regardless of
|
||||
// the service
|
||||
// and region by choosing Static type. Alternatively, you can
|
||||
// provide
|
||||
// configuration for dynamically resolving the URL with the config
|
||||
// you provide
|
||||
// once you set the type as Dynamic.
|
||||
type: "Static" | "Dynamic"
|
||||
}
|
||||
}
|
||||
|
||||
// Whether to enable the request to use path-style addressing,
|
||||
// i.e., https://s3.amazonaws.com/BUCKET/KEY.
|
||||
s3_use_path_style?: bool
|
||||
|
||||
// Whether to skip credentials validation via the STS API.
|
||||
// This can be useful for testing and for AWS API implementations
|
||||
// that do not have STS available.
|
||||
skip_credentials_validation?: bool
|
||||
|
||||
// Whether to skip the AWS Metadata API check
|
||||
// Useful for AWS API implementations that do not have a metadata
|
||||
// API endpoint.
|
||||
skip_metadata_api_check?: bool
|
||||
|
||||
// Whether to skip validation of provided region name.
|
||||
// Useful for AWS-like implementations that use their own region
|
||||
// names or to bypass the validation for
|
||||
// regions that aren't publicly available yet.
|
||||
skip_region_validation?: bool
|
||||
|
||||
// Whether to skip requesting the account ID.
|
||||
// Useful for AWS API implementations that do not have the IAM,
|
||||
// STS API, or metadata API
|
||||
skip_requesting_account_id?: bool
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
|
||||
|
||||
package v1alpha2
|
||||
|
||||
import "google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
// Label is an arbitrary unique identifier internal to holos itself. The holos
|
||||
// cli is expected to never write a Label value to rendered output files,
|
||||
// therefore use a [Label] then the identifier must be unique and internal.
|
||||
// Defined as a type for clarity and type checking.
|
||||
//
|
||||
// A Label is useful to convert a CUE struct to a list, for example producing a list of [APIObject] resources from an [APIObjectMap]. A CUE struct using
|
||||
// Label keys is guaranteed to not lose data when rendering output because a
|
||||
// Label is expected to never be written to the final output.
|
||||
#Label: string
|
||||
|
||||
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
|
||||
#Kind: string
|
||||
|
||||
// APIObject represents the most basic generic form of a single kubernetes api
|
||||
// object. Represented as a JSON object internally for compatibility between
|
||||
// tools, for example loading from CUE.
|
||||
#APIObject: structpb.#Struct
|
||||
|
||||
// APIObjectMap represents the marshalled yaml representation of kubernetes api
|
||||
// objects. Do not produce an APIObjectMap directly, instead use [APIObjects]
|
||||
// to produce the marshalled yaml representation from CUE data, then provide the
|
||||
// result to [HolosComponent].
|
||||
#APIObjectMap: {[string]: [string]: string}
|
||||
|
||||
// APIObjects represents Kubernetes API objects defined directly from CUE code.
|
||||
// Useful to mix in resources to any kind of [HolosComponent], for example
|
||||
// adding an ExternalSecret resource to a [HelmChart].
|
||||
//
|
||||
// [Kind] must be the resource kind, e.g. Deployment or Service.
|
||||
//
|
||||
// [Label] is an arbitrary internal identifier to uniquely identify the resource
|
||||
// within the context of a `holos` command. Holos will never write the
|
||||
// intermediate label to rendered output.
|
||||
//
|
||||
// Refer to [HolosComponent] which accepts an [APIObjectMap] field provided by
|
||||
// [APIObjects].
|
||||
#APIObjects: {
|
||||
apiObjects: {[string]: [string]: #APIObject} @go(APIObjects,map[Kind]map[Label]APIObject)
|
||||
apiObjectMap: #APIObjectMap @go(APIObjectMap)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
|
||||
|
||||
package v1alpha2
|
||||
|
||||
// FilePath represents a file path.
|
||||
#FilePath: string
|
||||
|
||||
// FileContent represents file contents.
|
||||
#FileContent: string
|
||||
|
||||
// FileContentMap represents a mapping of file paths to file contents. Paths
|
||||
// are relative to the `holos` output "deploy" directory, and may contain
|
||||
// sub-directories.
|
||||
#FileContentMap: {[string]: #FileContent}
|
||||
|
||||
// BuildPlan represents a build plan for the holos cli to execute. The purpose
|
||||
// of a BuildPlan is to define one or more [HolosComponent] kinds. For example a
|
||||
// [HelmChart], [KustomizeBuild], or [KubernetesObjects].
|
||||
//
|
||||
// A BuildPlan usually has an additional empty [KubernetesObjects] for the
|
||||
// purpose of using the [HolosComponent] DeployFiles field to deploy an ArgoCD
|
||||
// or Flux gitops resource for the holos component.
|
||||
#BuildPlan: {
|
||||
kind: string & "BuildPlan" @go(Kind)
|
||||
apiVersion: string & (string | *"v1alpha2") @go(APIVersion)
|
||||
spec: #BuildPlanSpec @go(Spec)
|
||||
}
|
||||
|
||||
// BuildPlanSpec represents the specification of the build plan.
|
||||
#BuildPlanSpec: {
|
||||
// Disabled causes the holos cli to take no action over the [BuildPlan].
|
||||
disabled?: bool @go(Disabled)
|
||||
|
||||
// Components represents multiple [HolosComponent] kinds to manage.
|
||||
components?: #BuildPlanComponents @go(Components)
|
||||
}
|
||||
|
||||
#BuildPlanComponents: {
|
||||
resources?: {[string]: #KubernetesObjects} @go(Resources,map[Label]KubernetesObjects)
|
||||
kubernetesObjectsList?: [...#KubernetesObjects] @go(KubernetesObjectsList,[]KubernetesObjects)
|
||||
helmChartList?: [...#HelmChart] @go(HelmChartList,[]HelmChart)
|
||||
kustomizeBuildList?: [...#KustomizeBuild] @go(KustomizeBuildList,[]KustomizeBuild)
|
||||
}
|
||||
|
||||
// HolosComponent defines the fields common to all holos component kinds. Every
|
||||
// holos component kind should embed HolosComponent.
|
||||
#HolosComponent: {
|
||||
// Kind is a string value representing the resource this object represents.
|
||||
kind: string @go(Kind)
|
||||
|
||||
// APIVersion represents the versioned schema of this representation of an object.
|
||||
apiVersion: string & (string | *"v1alpha2") @go(APIVersion)
|
||||
|
||||
// Metadata represents data about the holos component such as the Name.
|
||||
metadata: #Metadata @go(Metadata)
|
||||
|
||||
// APIObjectMap holds the marshalled representation of api objects. Useful to
|
||||
// mix in resources to each HolosComponent type, for example adding an
|
||||
// ExternalSecret to a HelmChart HolosComponent. Refer to [APIObjects].
|
||||
apiObjectMap?: #APIObjectMap @go(APIObjectMap)
|
||||
|
||||
// DeployFiles represents file paths relative to the cluster deploy directory
|
||||
// with the value representing the file content. Intended for defining the
|
||||
// ArgoCD Application resource or Flux Kustomization resource from within CUE,
|
||||
// but may be used to render any file related to the build plan from CUE.
|
||||
deployFiles?: #FileContentMap @go(DeployFiles)
|
||||
|
||||
// Kustomize represents a kubectl kustomize build post-processing step.
|
||||
kustomize?: #Kustomize @go(Kustomize)
|
||||
|
||||
// Skip causes holos to take no action regarding this component.
|
||||
skip: bool & (bool | *false) @go(Skip)
|
||||
}
|
||||
|
||||
// Metadata represents data about the holos component such as the Name.
|
||||
#Metadata: {
|
||||
// Name represents the name of the holos component.
|
||||
name: string @go(Name)
|
||||
|
||||
// Namespace is the primary namespace of the holos component. A holos
|
||||
// component may manage resources in multiple namespaces, in this case
|
||||
// consider setting the component namespace to default.
|
||||
//
|
||||
// This field is optional because not all resources require a namespace,
|
||||
// particularly CRD's and DeployFiles functionality.
|
||||
// +optional
|
||||
namespace?: string @go(Namespace)
|
||||
}
|
||||
|
||||
// Kustomize represents resources necessary to execute a kustomize build.
|
||||
// Intended for at least two use cases:
|
||||
//
|
||||
// 1. Process a [KustomizeBuild] [HolosComponent] which represents raw yaml
|
||||
// file resources in a holos component directory.
|
||||
// 2. Post process a [HelmChart] [HolosComponent] to inject istio, patch jobs,
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
|
||||
|
||||
package v1alpha2
|
||||
|
||||
#APIVersion: "v1alpha2"
|
||||
#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,52 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
|
||||
|
||||
package v1alpha2
|
||||
|
||||
import "google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
#PlatformMetadata: {
|
||||
// Name represents the Platform name.
|
||||
name: string @go(Name)
|
||||
}
|
||||
|
||||
// Platform represents a platform to manage. A Platform resource informs holos
|
||||
// which components to build. The platform resource also acts as a container
|
||||
// for the platform model form values provided by the PlatformService. The
|
||||
// primary use case is to collect the cluster names, cluster types, platform
|
||||
// model, and holos components to build into one resource.
|
||||
#Platform: {
|
||||
// Kind is a string value representing the resource this object represents.
|
||||
kind: string & "Platform" @go(Kind)
|
||||
|
||||
// APIVersion represents the versioned schema of this representation of an object.
|
||||
apiVersion: string & (string | *"v1alpha2") @go(APIVersion)
|
||||
|
||||
// Metadata represents data about the object such as the Name.
|
||||
metadata: #PlatformMetadata @go(Metadata)
|
||||
|
||||
// Spec represents the specification.
|
||||
spec: #PlatformSpec @go(Spec)
|
||||
}
|
||||
|
||||
// PlatformSpec represents the specification of a Platform. Think of a platform
|
||||
// specification as a list of platform components to apply to a list of
|
||||
// kubernetes clusters combined with the user-specified Platform Model.
|
||||
#PlatformSpec: {
|
||||
// Model represents the platform model holos gets from from the
|
||||
// PlatformService.GetPlatform rpc method and provides to CUE using a tag.
|
||||
model: structpb.#Struct @go(Model)
|
||||
|
||||
// Components represents a list of holos components to manage.
|
||||
components: [...#PlatformSpecComponent] @go(Components,[]PlatformSpecComponent)
|
||||
}
|
||||
|
||||
// PlatformSpecComponent represents a holos component to build or render.
|
||||
#PlatformSpecComponent: {
|
||||
// Path is the path of the component relative to the platform root.
|
||||
path: string @go(Path)
|
||||
|
||||
// Cluster is the cluster name to provide when rendering the component.
|
||||
cluster: string @go(Cluster)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
|
||||
|
||||
// Package v1alpha2 contains the core API contract between the holos cli and CUE
|
||||
// configuration code. Platform designers, operators, and software developers
|
||||
// use this API to write configuration in CUE which `holos` loads. The overall
|
||||
// shape of the API defines imperative actions `holos` should carry out to
|
||||
// render the complete yaml that represents a Platform.
|
||||
//
|
||||
// [Platform] defines the complete configuration of a platform. With the holos
|
||||
// reference platform this takes the shape of one management cluster and at
|
||||
// least two workload cluster. Each cluster has multiple [HolosComponent]
|
||||
// resources applied to it.
|
||||
//
|
||||
// Each holos component path, e.g. `components/namespaces` produces exactly one
|
||||
// [BuildPlan] which in turn contains a set of [HolosComponent] kinds.
|
||||
//
|
||||
// The primary kinds of [HolosComponent] are:
|
||||
//
|
||||
// 1. [HelmChart] to render config from a helm chart.
|
||||
// 2. [KustomizeBuild] to render config from [Kustomize]
|
||||
// 3. [KubernetesObjects] to render [APIObjects] defined directly in CUE
|
||||
// configuration.
|
||||
//
|
||||
// Note that Holos operates as a data pipeline, so the output of a [HelmChart]
|
||||
// may be provided to [Kustomize] for post-processing.
|
||||
package v1alpha2
|
||||
@@ -0,0 +1,47 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
|
||||
|
||||
package v1alpha2
|
||||
|
||||
// HelmChart represents a holos component which wraps around an upstream helm
|
||||
// chart. Holos orchestrates helm by providing values obtained from CUE,
|
||||
// renders the output using `helm template`, then post-processes the helm output
|
||||
// yaml using the general functionality provided by [HolosComponent], for
|
||||
// example [Kustomize] post-rendering and mixing in additional kubernetes api
|
||||
// objects.
|
||||
#HelmChart: {
|
||||
#HolosComponent
|
||||
kind: string & "HelmChart" @go(Kind)
|
||||
|
||||
// Chart represents a helm chart to manage.
|
||||
chart: #Chart @go(Chart)
|
||||
|
||||
// ValuesContent represents the values.yaml file holos passes to the `helm
|
||||
// template` command.
|
||||
valuesContent: string @go(ValuesContent)
|
||||
|
||||
// EnableHooks enables helm hooks when executing the `helm template` command.
|
||||
enableHooks: bool & (bool | *false) @go(EnableHooks)
|
||||
}
|
||||
|
||||
// Chart represents a helm chart.
|
||||
#Chart: {
|
||||
// Name represents the chart name.
|
||||
name: string @go(Name)
|
||||
|
||||
// Version represents the chart version.
|
||||
version: string @go(Version)
|
||||
|
||||
// Release represents the chart release when executing helm template.
|
||||
release: string @go(Release)
|
||||
|
||||
// Repository represents the repository to fetch the chart from.
|
||||
repository?: #Repository @go(Repository)
|
||||
}
|
||||
|
||||
// Repository represents a helm chart repository.
|
||||
#Repository: {
|
||||
name: string @go(Name)
|
||||
url: string @go(URL)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
|
||||
|
||||
package v1alpha2
|
||||
|
||||
#KubernetesObjectsKind: "KubernetesObjects"
|
||||
|
||||
// KubernetesObjects represents a [HolosComponent] composed of Kubernetes API
|
||||
// objects provided directly from CUE using [APIObjects].
|
||||
#KubernetesObjects: {
|
||||
#HolosComponent
|
||||
kind: string & "KubernetesObjects" @go(Kind)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
|
||||
|
||||
package v1alpha2
|
||||
|
||||
// KustomizeBuild represents a [HolosComponent] that renders plain yaml files in
|
||||
// the holos component directory using `kubectl kustomize build`.
|
||||
#KustomizeBuild: {
|
||||
#HolosComponent
|
||||
kind: string & "KustomizeBuild" @go(Kind)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/meta/v1alpha2
|
||||
|
||||
package v1alpha2
|
||||
|
||||
// TypeMeta describes an individual object in an API response or request with
|
||||
// strings representing the type of the object and its API schema version.
|
||||
// Structures that are versioned or persisted should inline TypeMeta.
|
||||
#TypeMeta: {
|
||||
// Kind is a string value representing the resource this object represents.
|
||||
kind: string @go(Kind)
|
||||
|
||||
// APIVersion defines the versioned schema of this representation of an object.
|
||||
apiVersion: string & (string | *"v1alpha2") @go(APIVersion)
|
||||
}
|
||||
|
||||
// Discriminator discriminates the kind of an api object.
|
||||
#Discriminator: _
|
||||
|
||||
// ObjectMeta represents metadata of a holos component object. The fields are a
|
||||
// copy of upstream kubernetes api machinery but are 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)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,132 @@
|
||||
// Code generated by timoni. DO NOT EDIT.
|
||||
|
||||
//timoni:generate timoni vendor crd -f https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_functions.yaml
|
||||
|
||||
package v1beta1
|
||||
|
||||
import "strings"
|
||||
|
||||
// A Function installs an OCI compatible Crossplane package,
|
||||
// extending
|
||||
// Crossplane with support for a new kind of composition function.
|
||||
//
|
||||
//
|
||||
// Read the Crossplane documentation for
|
||||
// [more information about
|
||||
// Functions](https://docs.crossplane.io/latest/concepts/composition-functions).
|
||||
#Function: {
|
||||
// APIVersion defines the versioned schema of this representation
|
||||
// of an object.
|
||||
// Servers should convert recognized schemas to the latest
|
||||
// internal value, and
|
||||
// may reject unrecognized values.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
apiVersion: "pkg.crossplane.io/v1beta1"
|
||||
|
||||
// Kind is a string value representing the REST resource this
|
||||
// object represents.
|
||||
// Servers may infer this from the endpoint the client submits
|
||||
// requests to.
|
||||
// Cannot be updated.
|
||||
// In CamelCase.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
kind: "Function"
|
||||
metadata!: {
|
||||
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
namespace?: strings.MaxRunes(63) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
labels?: {
|
||||
[string]: string
|
||||
}
|
||||
annotations?: {
|
||||
[string]: string
|
||||
}
|
||||
}
|
||||
|
||||
// FunctionSpec specifies the configuration of a Function.
|
||||
spec!: #FunctionSpec
|
||||
}
|
||||
|
||||
// FunctionSpec specifies the configuration of a Function.
|
||||
#FunctionSpec: {
|
||||
// Map of string keys and values that can be used to organize and
|
||||
// categorize
|
||||
// (scope and select) objects. May match selectors of replication
|
||||
// controllers
|
||||
// and services.
|
||||
// More info:
|
||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
commonLabels?: {
|
||||
[string]: string
|
||||
}
|
||||
controllerConfigRef?: {
|
||||
// Name of the ControllerConfig.
|
||||
name: string
|
||||
}
|
||||
|
||||
// IgnoreCrossplaneConstraints indicates to the package manager
|
||||
// whether to
|
||||
// honor Crossplane version constrains specified by the package.
|
||||
// Default is false.
|
||||
ignoreCrossplaneConstraints?: bool | *false
|
||||
|
||||
// Package is the name of the package that is being requested.
|
||||
package: string
|
||||
|
||||
// PackagePullPolicy defines the pull policy for the package.
|
||||
// Default is IfNotPresent.
|
||||
packagePullPolicy?: string | *"IfNotPresent"
|
||||
|
||||
// PackagePullSecrets are named secrets in the same namespace that
|
||||
// can be used
|
||||
// to fetch packages from private registries.
|
||||
packagePullSecrets?: [...{
|
||||
// Name of the referent.
|
||||
// More info:
|
||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
// TODO: Add other useful fields. apiVersion, kind, uid?
|
||||
name?: string
|
||||
}]
|
||||
|
||||
// RevisionActivationPolicy specifies how the package controller
|
||||
// should
|
||||
// update from one revision to the next. Options are Automatic or
|
||||
// Manual.
|
||||
// Default is Automatic.
|
||||
revisionActivationPolicy?: string | *"Automatic"
|
||||
|
||||
// RevisionHistoryLimit dictates how the package controller cleans
|
||||
// up old
|
||||
// inactive package revisions.
|
||||
// Defaults to 1. Can be disabled by explicitly setting to 0.
|
||||
revisionHistoryLimit?: int | *1
|
||||
|
||||
// RuntimeConfigRef references a RuntimeConfig resource that will
|
||||
// be used
|
||||
// to configure the package runtime.
|
||||
runtimeConfigRef?: {
|
||||
// API version of the referent.
|
||||
apiVersion?: string | *"pkg.crossplane.io/v1beta1"
|
||||
|
||||
// Kind of the referent.
|
||||
kind?: string | *"DeploymentRuntimeConfig"
|
||||
|
||||
// Name of the RuntimeConfig.
|
||||
name: string
|
||||
} | *{
|
||||
name: "default"
|
||||
}
|
||||
|
||||
// SkipDependencyResolution indicates to the package manager
|
||||
// whether to skip
|
||||
// resolving dependencies for a package. Setting this value to
|
||||
// true may have
|
||||
// unintended consequences.
|
||||
// Default is false.
|
||||
skipDependencyResolution?: bool | *false
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Code generated by timoni. DO NOT EDIT.
|
||||
|
||||
//timoni:generate timoni vendor crd -f https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_providers.yaml
|
||||
|
||||
package v1
|
||||
|
||||
import "strings"
|
||||
|
||||
// A Provider installs an OCI compatible Crossplane package,
|
||||
// extending
|
||||
// Crossplane with support for new kinds of managed resources.
|
||||
//
|
||||
//
|
||||
// Read the Crossplane documentation for
|
||||
// [more information about
|
||||
// Providers](https://docs.crossplane.io/latest/concepts/providers).
|
||||
#Provider: {
|
||||
// APIVersion defines the versioned schema of this representation
|
||||
// of an object.
|
||||
// Servers should convert recognized schemas to the latest
|
||||
// internal value, and
|
||||
// may reject unrecognized values.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
apiVersion: "pkg.crossplane.io/v1"
|
||||
|
||||
// Kind is a string value representing the REST resource this
|
||||
// object represents.
|
||||
// Servers may infer this from the endpoint the client submits
|
||||
// requests to.
|
||||
// Cannot be updated.
|
||||
// In CamelCase.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
kind: "Provider"
|
||||
metadata!: {
|
||||
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
namespace?: strings.MaxRunes(63) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
labels?: {
|
||||
[string]: string
|
||||
}
|
||||
annotations?: {
|
||||
[string]: string
|
||||
}
|
||||
}
|
||||
|
||||
// ProviderSpec specifies details about a request to install a
|
||||
// provider to
|
||||
// Crossplane.
|
||||
spec!: #ProviderSpec
|
||||
}
|
||||
|
||||
// ProviderSpec specifies details about a request to install a
|
||||
// provider to
|
||||
// Crossplane.
|
||||
#ProviderSpec: {
|
||||
// Map of string keys and values that can be used to organize and
|
||||
// categorize
|
||||
// (scope and select) objects. May match selectors of replication
|
||||
// controllers
|
||||
// and services.
|
||||
// More info:
|
||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
commonLabels?: {
|
||||
[string]: string
|
||||
}
|
||||
controllerConfigRef?: {
|
||||
// Name of the ControllerConfig.
|
||||
name: string
|
||||
}
|
||||
|
||||
// IgnoreCrossplaneConstraints indicates to the package manager
|
||||
// whether to
|
||||
// honor Crossplane version constrains specified by the package.
|
||||
// Default is false.
|
||||
ignoreCrossplaneConstraints?: bool | *false
|
||||
|
||||
// Package is the name of the package that is being requested.
|
||||
package: string
|
||||
|
||||
// PackagePullPolicy defines the pull policy for the package.
|
||||
// Default is IfNotPresent.
|
||||
packagePullPolicy?: string | *"IfNotPresent"
|
||||
|
||||
// PackagePullSecrets are named secrets in the same namespace that
|
||||
// can be used
|
||||
// to fetch packages from private registries.
|
||||
packagePullSecrets?: [...{
|
||||
// Name of the referent.
|
||||
// More info:
|
||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
// TODO: Add other useful fields. apiVersion, kind, uid?
|
||||
name?: string
|
||||
}]
|
||||
|
||||
// RevisionActivationPolicy specifies how the package controller
|
||||
// should
|
||||
// update from one revision to the next. Options are Automatic or
|
||||
// Manual.
|
||||
// Default is Automatic.
|
||||
revisionActivationPolicy?: string | *"Automatic"
|
||||
|
||||
// RevisionHistoryLimit dictates how the package controller cleans
|
||||
// up old
|
||||
// inactive package revisions.
|
||||
// Defaults to 1. Can be disabled by explicitly setting to 0.
|
||||
revisionHistoryLimit?: int | *1
|
||||
|
||||
// RuntimeConfigRef references a RuntimeConfig resource that will
|
||||
// be used
|
||||
// to configure the package runtime.
|
||||
runtimeConfigRef?: {
|
||||
// API version of the referent.
|
||||
apiVersion?: string | *"pkg.crossplane.io/v1beta1"
|
||||
|
||||
// Kind of the referent.
|
||||
kind?: string | *"DeploymentRuntimeConfig"
|
||||
|
||||
// Name of the RuntimeConfig.
|
||||
name: string
|
||||
} | *{
|
||||
name: "default"
|
||||
}
|
||||
|
||||
// SkipDependencyResolution indicates to the package manager
|
||||
// whether to skip
|
||||
// resolving dependencies for a package. Setting this value to
|
||||
// true may have
|
||||
// unintended consequences.
|
||||
// Default is false.
|
||||
skipDependencyResolution?: bool | *false
|
||||
}
|
||||
@@ -32,9 +32,7 @@ import "strings"
|
||||
// Configuration for access control on workloads. See more details
|
||||
// at:
|
||||
// https://istio.io/docs/reference/config/security/authorization-policy.html
|
||||
#AuthorizationPolicySpec: ({} | {
|
||||
provider: _
|
||||
}) & {
|
||||
#AuthorizationPolicySpec: {
|
||||
// Optional.
|
||||
//
|
||||
// Valid Options: ALLOW, DENY, AUDIT, CUSTOM
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
module: "user.holos.run/platform"
|
||||
language: version: "v0.9.2"
|
||||
|
||||
@@ -78,13 +78,13 @@ let FormBuilder = v1.#FormBuilder & {
|
||||
multiple: true
|
||||
selectAllOption: "Select All"
|
||||
options: [
|
||||
{value: "aws", label: "Amazon Web Services"},
|
||||
{value: "gcp", label: "Google Cloud Platform"},
|
||||
{value: "azure", label: "Microsoft Azure"},
|
||||
{value: "aws", label: "Amazon Web Services"},
|
||||
{value: "gcp", label: "Google Cloud Platform"},
|
||||
{value: "azure", label: "Microsoft Azure"},
|
||||
{value: "cloudflare", label: "Cloudflare"},
|
||||
{value: "github", label: "GitHub"},
|
||||
{value: "ois", label: "Open Infrastructure Services"},
|
||||
{value: "onprem", label: "On Premises", disabled: true},
|
||||
{value: "github", label: "GitHub"},
|
||||
{value: "ois", label: "Open Infrastructure Services"},
|
||||
{value: "onprem", label: "On Premises", disabled: true},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -281,73 +281,73 @@ let FormBuilder = v1.#FormBuilder & {
|
||||
}
|
||||
|
||||
let GCPRegions = [
|
||||
{value: "africa-south1", label: "africa-south1"},
|
||||
{value: "asia-east1", label: "asia-east1"},
|
||||
{value: "asia-east2", label: "asia-east2"},
|
||||
{value: "asia-northeast1", label: "asia-northeast1"},
|
||||
{value: "asia-northeast2", label: "asia-northeast2"},
|
||||
{value: "asia-northeast3", label: "asia-northeast3"},
|
||||
{value: "asia-south1", label: "asia-south1"},
|
||||
{value: "asia-south2", label: "asia-south2"},
|
||||
{value: "asia-southeast1", label: "asia-southeast1"},
|
||||
{value: "asia-southeast2", label: "asia-southeast2"},
|
||||
{value: "australia-southeast1", label: "australia-southeast1"},
|
||||
{value: "australia-southeast2", label: "australia-southeast2"},
|
||||
{value: "europe-central2", label: "europe-central2"},
|
||||
{value: "europe-north1", label: "europe-north1"},
|
||||
{value: "europe-southwest1", label: "europe-southwest1"},
|
||||
{value: "europe-west1", label: "europe-west1"},
|
||||
{value: "europe-west10", label: "europe-west10"},
|
||||
{value: "europe-west12", label: "europe-west12"},
|
||||
{value: "europe-west2", label: "europe-west2"},
|
||||
{value: "europe-west3", label: "europe-west3"},
|
||||
{value: "europe-west4", label: "europe-west4"},
|
||||
{value: "europe-west6", label: "europe-west6"},
|
||||
{value: "europe-west8", label: "europe-west8"},
|
||||
{value: "europe-west9", label: "europe-west9"},
|
||||
{value: "me-central1", label: "me-central1"},
|
||||
{value: "me-central2", label: "me-central2"},
|
||||
{value: "me-west1", label: "me-west1"},
|
||||
{value: "africa-south1", label: "africa-south1"},
|
||||
{value: "asia-east1", label: "asia-east1"},
|
||||
{value: "asia-east2", label: "asia-east2"},
|
||||
{value: "asia-northeast1", label: "asia-northeast1"},
|
||||
{value: "asia-northeast2", label: "asia-northeast2"},
|
||||
{value: "asia-northeast3", label: "asia-northeast3"},
|
||||
{value: "asia-south1", label: "asia-south1"},
|
||||
{value: "asia-south2", label: "asia-south2"},
|
||||
{value: "asia-southeast1", label: "asia-southeast1"},
|
||||
{value: "asia-southeast2", label: "asia-southeast2"},
|
||||
{value: "australia-southeast1", label: "australia-southeast1"},
|
||||
{value: "australia-southeast2", label: "australia-southeast2"},
|
||||
{value: "europe-central2", label: "europe-central2"},
|
||||
{value: "europe-north1", label: "europe-north1"},
|
||||
{value: "europe-southwest1", label: "europe-southwest1"},
|
||||
{value: "europe-west1", label: "europe-west1"},
|
||||
{value: "europe-west10", label: "europe-west10"},
|
||||
{value: "europe-west12", label: "europe-west12"},
|
||||
{value: "europe-west2", label: "europe-west2"},
|
||||
{value: "europe-west3", label: "europe-west3"},
|
||||
{value: "europe-west4", label: "europe-west4"},
|
||||
{value: "europe-west6", label: "europe-west6"},
|
||||
{value: "europe-west8", label: "europe-west8"},
|
||||
{value: "europe-west9", label: "europe-west9"},
|
||||
{value: "me-central1", label: "me-central1"},
|
||||
{value: "me-central2", label: "me-central2"},
|
||||
{value: "me-west1", label: "me-west1"},
|
||||
{value: "northamerica-northeast1", label: "northamerica-northeast1"},
|
||||
{value: "northamerica-northeast2", label: "northamerica-northeast2"},
|
||||
{value: "southamerica-east1", label: "southamerica-east1"},
|
||||
{value: "southamerica-west1", label: "southamerica-west1"},
|
||||
{value: "us-central1", label: "us-central1"},
|
||||
{value: "us-east1", label: "us-east1"},
|
||||
{value: "us-east4", label: "us-east4"},
|
||||
{value: "us-east5", label: "us-east5"},
|
||||
{value: "us-south1", label: "us-south1"},
|
||||
{value: "us-west1", label: "us-west1"},
|
||||
{value: "us-west2", label: "us-west2"},
|
||||
{value: "us-west3", label: "us-west3"},
|
||||
{value: "us-west4", label: "us-west4"},
|
||||
{value: "southamerica-east1", label: "southamerica-east1"},
|
||||
{value: "southamerica-west1", label: "southamerica-west1"},
|
||||
{value: "us-central1", label: "us-central1"},
|
||||
{value: "us-east1", label: "us-east1"},
|
||||
{value: "us-east4", label: "us-east4"},
|
||||
{value: "us-east5", label: "us-east5"},
|
||||
{value: "us-south1", label: "us-south1"},
|
||||
{value: "us-west1", label: "us-west1"},
|
||||
{value: "us-west2", label: "us-west2"},
|
||||
{value: "us-west3", label: "us-west3"},
|
||||
{value: "us-west4", label: "us-west4"},
|
||||
]
|
||||
|
||||
let AWSRegions = [
|
||||
{value: "us-east-1", label: "N. Virginia (us-east-1)"},
|
||||
{value: "us-east-2", label: "Ohio (us-east-2)"},
|
||||
{value: "us-west-1", label: "N. California (us-west-1)"},
|
||||
{value: "us-west-2", label: "Oregon (us-west-2)"},
|
||||
{value: "us-gov-west1", label: "US GovCloud West (us-gov-west1)"},
|
||||
{value: "us-gov-east1", label: "US GovCloud East (us-gov-east1)"},
|
||||
{value: "ca-central-1", label: "Canada (ca-central-1)"},
|
||||
{value: "eu-north-1", label: "Stockholm (eu-north-1)"},
|
||||
{value: "eu-west-1", label: "Ireland (eu-west-1)"},
|
||||
{value: "eu-west-2", label: "London (eu-west-2)"},
|
||||
{value: "eu-west-3", label: "Paris (eu-west-3)"},
|
||||
{value: "eu-central-1", label: "Frankfurt (eu-central-1)"},
|
||||
{value: "eu-south-1", label: "Milan (eu-south-1)"},
|
||||
{value: "af-south-1", label: "Cape Town (af-south-1)"},
|
||||
{value: "us-east-1", label: "N. Virginia (us-east-1)"},
|
||||
{value: "us-east-2", label: "Ohio (us-east-2)"},
|
||||
{value: "us-west-1", label: "N. California (us-west-1)"},
|
||||
{value: "us-west-2", label: "Oregon (us-west-2)"},
|
||||
{value: "us-gov-west1", label: "US GovCloud West (us-gov-west1)"},
|
||||
{value: "us-gov-east1", label: "US GovCloud East (us-gov-east1)"},
|
||||
{value: "ca-central-1", label: "Canada (ca-central-1)"},
|
||||
{value: "eu-north-1", label: "Stockholm (eu-north-1)"},
|
||||
{value: "eu-west-1", label: "Ireland (eu-west-1)"},
|
||||
{value: "eu-west-2", label: "London (eu-west-2)"},
|
||||
{value: "eu-west-3", label: "Paris (eu-west-3)"},
|
||||
{value: "eu-central-1", label: "Frankfurt (eu-central-1)"},
|
||||
{value: "eu-south-1", label: "Milan (eu-south-1)"},
|
||||
{value: "af-south-1", label: "Cape Town (af-south-1)"},
|
||||
{value: "ap-northeast-1", label: "Tokyo (ap-northeast-1)"},
|
||||
{value: "ap-northeast-2", label: "Seoul (ap-northeast-2)"},
|
||||
{value: "ap-northeast-3", label: "Osaka (ap-northeast-3)"},
|
||||
{value: "ap-southeast-1", label: "Singapore (ap-southeast-1)"},
|
||||
{value: "ap-southeast-2", label: "Sydney (ap-southeast-2)"},
|
||||
{value: "ap-east-1", label: "Hong Kong (ap-east-1)"},
|
||||
{value: "ap-south-1", label: "Mumbai (ap-south-1)"},
|
||||
{value: "me-south-1", label: "Bahrain (me-south-1)"},
|
||||
{value: "sa-east-1", label: "São Paulo (sa-east-1)"},
|
||||
{value: "cn-north-1", label: "Bejing (cn-north-1)"},
|
||||
{value: "ap-east-1", label: "Hong Kong (ap-east-1)"},
|
||||
{value: "ap-south-1", label: "Mumbai (ap-south-1)"},
|
||||
{value: "me-south-1", label: "Bahrain (me-south-1)"},
|
||||
{value: "sa-east-1", label: "São Paulo (sa-east-1)"},
|
||||
{value: "cn-north-1", label: "Bejing (cn-north-1)"},
|
||||
{value: "cn-northwest-1", label: "Ningxia (cn-northwest-1)"},
|
||||
{value: "ap-southeast-3", label: "Jakarta (ap-southeast-3)"},
|
||||
]
|
||||
|
||||
67
internal/generate/platforms/holos/apps/appinfo.cue
Normal file
67
internal/generate/platforms/holos/apps/appinfo.cue
Normal file
@@ -0,0 +1,67 @@
|
||||
package holos
|
||||
|
||||
import "strings"
|
||||
|
||||
// #AppInfo represents the data structure for an application deployed onto the
|
||||
// platform. This definition constraints the sechema defined at the root.
|
||||
_AppInfo: #AppInfo & {
|
||||
metadata: name: string
|
||||
metadata: namespace: "\(spec.env)-\(metadata.name)"
|
||||
|
||||
spec: env: string
|
||||
spec: region: hostname: spec.dns.segments._region
|
||||
spec: global: hostname: spec.dns.segments._global
|
||||
|
||||
spec: dns: segments: {
|
||||
env: [] | *[spec.env]
|
||||
name: [] | [string | *metadata.name]
|
||||
cluster: [] | *[_ClusterName]
|
||||
domain: [] | *[_Platform.Model.org.domain]
|
||||
_region: strings.Join(env+name+cluster+domain, ".")
|
||||
_global: strings.Join(env+name+domain, ".")
|
||||
}
|
||||
}
|
||||
|
||||
// #AppRoute represents the HTTPRoute resources in the namespace of the Gateway.
|
||||
#AppRoute: {
|
||||
AppInfo: #AppInfo
|
||||
|
||||
Resources: {
|
||||
HTTPRoute: (HTTPRouteApp & {Hostname: AppInfo.spec.region.hostname}).HTTPRoute
|
||||
HTTPRoute: (HTTPRouteApp & {Hostname: AppInfo.spec.global.hostname}).HTTPRoute
|
||||
}
|
||||
|
||||
let HTTPRouteApp = {
|
||||
Hostname: string
|
||||
|
||||
HTTPRoute: (Hostname): {
|
||||
metadata: namespace: #IstioGatewaysNamespace
|
||||
metadata: labels: AppInfo.metadata.labels
|
||||
metadata: annotations: AppInfo.metadata.annotations
|
||||
spec: hostnames: [Hostname]
|
||||
spec: parentRefs: [{
|
||||
name: "default"
|
||||
namespace: #IstioGatewaysNamespace
|
||||
}]
|
||||
spec: rules: [
|
||||
{
|
||||
matches: [{path: {type: "PathPrefix", value: "/"}}]
|
||||
backendRefs: [{
|
||||
name: AppInfo.metadata.name
|
||||
namespace: AppInfo.metadata.namespace
|
||||
port: AppInfo.spec.port
|
||||
}]
|
||||
},
|
||||
{
|
||||
// match the authproxy path prefix
|
||||
matches: [{path: {type: "PathPrefix", value: _AuthProxy.pathPrefix}}]
|
||||
backendRefs: [{
|
||||
name: _AuthProxy.metadata.name
|
||||
namespace: _AuthProxy.metadata.namespace
|
||||
port: _AuthProxy.servicePort
|
||||
}]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
3
internal/generate/platforms/holos/apps/dev/dev.cue
Normal file
3
internal/generate/platforms/holos/apps/dev/dev.cue
Normal file
@@ -0,0 +1,3 @@
|
||||
package holos
|
||||
|
||||
_AppInfo: #AppInfo & {spec: env: "dev"}
|
||||
@@ -0,0 +1,108 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
let Image = "quay.io/holos-run/holos:v0.83.1-7-gd9fe32b"
|
||||
|
||||
_AppInfo: spec: component: "app"
|
||||
|
||||
let Objects = {
|
||||
Name: _AppInfo.status.component
|
||||
Namespace: _AppInfo.metadata.namespace
|
||||
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
let Metadata = _AppInfo.metadata
|
||||
|
||||
Resources: {
|
||||
let MatchLabels = {"app.kubernetes.io/component": "server"}
|
||||
|
||||
// Grant the Gateway ns the ability to refer to the Service from HTTPRoutes.
|
||||
ReferenceGrant: (#IstioGatewaysNamespace): #ReferenceGrant
|
||||
|
||||
Service: holos: {
|
||||
apiVersion: "v1"
|
||||
metadata: Metadata
|
||||
spec: {
|
||||
type: "ClusterIP"
|
||||
selector: MatchLabels
|
||||
ports: [{
|
||||
appProtocol: "http2"
|
||||
name: "http"
|
||||
port: _AppInfo.spec.port
|
||||
protocol: "TCP"
|
||||
targetPort: _AppInfo.spec.port
|
||||
}, {
|
||||
appProtocol: "http"
|
||||
name: "metrics"
|
||||
port: 9090
|
||||
protocol: "TCP"
|
||||
targetPort: 9090
|
||||
}]
|
||||
}
|
||||
}
|
||||
Deployment: holos: {
|
||||
metadata: Metadata
|
||||
metadata: labels: MatchLabels
|
||||
spec: {
|
||||
selector: matchLabels: MatchLabels
|
||||
|
||||
template: metadata: labels: Metadata.labels
|
||||
template: metadata: labels: MatchLabels
|
||||
template: metadata: labels: "sidecar.istio.io/inject": "true"
|
||||
|
||||
strategy: rollingUpdate: maxSurge: 1
|
||||
strategy: rollingUpdate: maxUnavailable: 0
|
||||
template: {
|
||||
spec: {
|
||||
serviceAccountName: Metadata.name
|
||||
securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
containers: [
|
||||
{
|
||||
name: Metadata.name
|
||||
image: Image
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
command: [
|
||||
"/app/bin/holos",
|
||||
"server",
|
||||
"--log-format=json",
|
||||
"--oidc-issuer=\(_AuthProxy.issuerURL)",
|
||||
"--oidc-audience=\(_AuthProxy.projectID)",
|
||||
]
|
||||
env: [
|
||||
{
|
||||
name: "TZ"
|
||||
value: "America/Los_Angeles"
|
||||
},
|
||||
{
|
||||
name: "DATABASE_URL"
|
||||
valueFrom: secretKeyRef: {
|
||||
key: "uri"
|
||||
name: "holos-pguser-holos"
|
||||
}
|
||||
},
|
||||
]
|
||||
ports: [
|
||||
{
|
||||
containerPort: 3000
|
||||
name: "http"
|
||||
protocol: "TCP"
|
||||
},
|
||||
]
|
||||
securityContext: capabilities: drop: ["ALL"]
|
||||
securityContext: allowPrivilegeEscalation: false
|
||||
securityContext: runAsNonRoot: true
|
||||
resources: limits: {
|
||||
cpu: "0.5"
|
||||
memory: "512Mi"
|
||||
}
|
||||
resources: requests: resources.limits
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
internal/generate/platforms/holos/apps/dev/holos/holos.cue
Normal file
19
internal/generate/platforms/holos/apps/dev/holos/holos.cue
Normal file
@@ -0,0 +1,19 @@
|
||||
package holos
|
||||
|
||||
_AppInfo: #AppInfo & {
|
||||
metadata: name: "holos"
|
||||
metadata: labels: {
|
||||
"app.holos.run/environment": spec.env
|
||||
"app.holos.run/name": metadata.name
|
||||
"app.holos.run/component": spec.component
|
||||
"render.holos.run/component": status.component
|
||||
}
|
||||
|
||||
spec: env: string
|
||||
spec: port: 3000
|
||||
spec: component: "app" | "infra" | "routes"
|
||||
|
||||
spec: dns: segments: name: ["app"]
|
||||
|
||||
status: component: spec.env + "-" + metadata.name + "-" + spec.component
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
_AppInfo: spec: component: "infra"
|
||||
|
||||
let Objects = {
|
||||
Name: _AppInfo.status.component
|
||||
Namespace: _AppInfo.metadata.namespace
|
||||
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
let AWS_ACCOUNT = _Platform.Model.aws.accountNumber
|
||||
let Metadata = _AppInfo.metadata
|
||||
|
||||
Resources: {
|
||||
ServiceAccount: holos: {
|
||||
metadata: Metadata
|
||||
// TODO(jeff): The ecr-creds-refresher name should be refactored to a root
|
||||
// level private var so we can update it in one place.
|
||||
// Refer to ecr-creds-refresher.cue
|
||||
imagePullSecrets: [{name: "ecr-creds-\(AWS_ACCOUNT)"}]
|
||||
}
|
||||
|
||||
PostgresCluster: holos: {
|
||||
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
|
||||
metadata: name: "holos"
|
||||
metadata: Metadata
|
||||
spec: {
|
||||
image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.1-0"
|
||||
instances: [{
|
||||
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
|
||||
podAffinityTerm: {
|
||||
labelSelector: matchLabels: "postgres-operator.crunchydata.com/cluster": metadata.name
|
||||
topologyKey: "topology.kubernetes.io/zone"
|
||||
}
|
||||
weight: 1
|
||||
}]
|
||||
dataVolumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: "1Gi"
|
||||
}
|
||||
name: "db"
|
||||
replicas: 1
|
||||
}]
|
||||
port: 5432
|
||||
postgresVersion: 16
|
||||
users: [{
|
||||
databases: ["holos"]
|
||||
name: "holos"
|
||||
options: "SUPERUSER"
|
||||
}]
|
||||
backups: pgbackrest: {
|
||||
global: {
|
||||
"archive-async": "y"
|
||||
"archive-push-queue-max": "100MiB"
|
||||
"spool-path": "/pgdata/backups"
|
||||
}
|
||||
image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.47-2"
|
||||
repos: [{
|
||||
name: "repo1"
|
||||
volume: volumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: "1Gi"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
_AppInfo: spec: component: "routes"
|
||||
|
||||
let Objects = {
|
||||
Name: _AppInfo.status.component
|
||||
Namespace: #IstioGatewaysNamespace
|
||||
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
// HTTPRoute resources
|
||||
Resources: (#AppRoute & {AppInfo: _AppInfo}).Resources
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package holos
|
||||
|
||||
import (
|
||||
"encoding/yaml"
|
||||
v1 "github.com/holos-run/holos/api/v1alpha1"
|
||||
core "github.com/holos-run/holos/api/core/v1alpha2"
|
||||
|
||||
kc "sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
@@ -26,6 +26,13 @@ import (
|
||||
es "external-secrets.io/externalsecret/v1beta1"
|
||||
|
||||
pc "postgres-operator.crunchydata.com/postgrescluster/v1beta1"
|
||||
|
||||
app "argoproj.io/application/v1alpha1"
|
||||
|
||||
cpv1 "pkg.crossplane.io/provider/v1"
|
||||
cpdrcv1beta1 "pkg.crossplane.io/deploymentruntimeconfig/v1beta1"
|
||||
cpfuncv1beta1 "pkg.crossplane.io/function/v1beta1"
|
||||
cpawspcv1beta1 "aws.upbound.io/providerconfig/v1beta1"
|
||||
)
|
||||
|
||||
// #Resources represents kubernetes api objects output along side a build plan.
|
||||
@@ -61,6 +68,12 @@ import (
|
||||
Gateway: [string]: gwv1.#Gateway & {
|
||||
spec: gatewayClassName: string | *"istio"
|
||||
}
|
||||
|
||||
// Crossplane resources
|
||||
DeploymentRuntimeConfig: [string]: cpdrcv1beta1.#DeploymentRuntimeConfig
|
||||
Provider: [string]: cpv1.#Provider
|
||||
Function: [string]: cpfuncv1beta1.#Function
|
||||
ProviderConfig: [string]: cpawspcv1beta1.#ProviderConfig
|
||||
}
|
||||
|
||||
#ReferenceGrant: rgv1.#ReferenceGrant & {
|
||||
@@ -90,13 +103,13 @@ import (
|
||||
|
||||
Values: {...}
|
||||
|
||||
Chart: v1.#HelmChart & {
|
||||
metadata: name: string | *Name
|
||||
namespace: string | *Namespace
|
||||
chart: name: string | *Name
|
||||
chart: release: chart.name
|
||||
chart: version: string | *Version
|
||||
chart: repository: Repo
|
||||
Chart: core.#HelmChart & {
|
||||
metadata: name: string | *Name
|
||||
metadata: namespace: string | *Namespace
|
||||
chart: name: string | *Name
|
||||
chart: release: chart.name
|
||||
chart: version: string | *Version
|
||||
chart: repository: Repo
|
||||
|
||||
// Render the values to yaml for holos to provide to helm.
|
||||
valuesContent: yaml.Marshal(Values)
|
||||
@@ -106,15 +119,15 @@ import (
|
||||
// resourcesFile represents the file helm output is written two and
|
||||
// kustomize reads from. Typically "resources.yaml" but referenced as a
|
||||
// constant to ensure the holos cli uses the same file.
|
||||
resourcesFile: v1.#ResourcesFile
|
||||
kustomize: resourcesFile: core.#ResourcesFile
|
||||
// kustomizeFiles represents the files in a kustomize directory tree.
|
||||
kustomizeFiles: v1.#FileContentMap
|
||||
kustomize: kustomizeFiles: core.#FileContentMap
|
||||
for FileName, Object in KustomizeFiles {
|
||||
kustomizeFiles: "\(FileName)": yaml.Marshal(Object)
|
||||
kustomize: kustomizeFiles: "\(FileName)": yaml.Marshal(Object)
|
||||
}
|
||||
}
|
||||
|
||||
apiObjectMap: (v1.#APIObjects & {apiObjects: Resources}).apiObjectMap
|
||||
apiObjectMap: (#APIObjects & {apiObjects: Resources}).apiObjectMap
|
||||
}
|
||||
|
||||
// EnableKustomizePostProcessor processes helm output with kustomize if true.
|
||||
@@ -134,7 +147,7 @@ import (
|
||||
"kustomization.yaml": kc.#Kustomization & {
|
||||
apiVersion: "kustomize.config.k8s.io/v1beta1"
|
||||
kind: "Kustomization"
|
||||
resources: [v1.#ResourcesFile, for FileName, _ in KustomizeResources {FileName}]
|
||||
resources: [core.#ResourcesFile, for FileName, _ in KustomizeResources {FileName}]
|
||||
patches: [for x in KustomizePatches {x}]
|
||||
}
|
||||
}
|
||||
@@ -146,7 +159,9 @@ import (
|
||||
KustomizeResources: [FileName=string]: {...}
|
||||
|
||||
// output represents the build plan provided to the holos cli.
|
||||
Output: v1.#BuildPlan & {
|
||||
Output: #BuildPlan & {
|
||||
_Name: Name
|
||||
_Namespace: Namespace
|
||||
spec: components: helmChartList: [Chart]
|
||||
}
|
||||
}
|
||||
@@ -156,12 +171,13 @@ import (
|
||||
// Name represents the holos component name
|
||||
Name: string
|
||||
|
||||
Kustomization: v1.#KustomizeBuild & {
|
||||
Kustomization: core.#KustomizeBuild & {
|
||||
metadata: name: string | *Name
|
||||
}
|
||||
|
||||
// output represents the build plan provided to the holos cli.
|
||||
Output: v1.#BuildPlan & {
|
||||
Output: #BuildPlan & {
|
||||
_Name: Name
|
||||
spec: components: kustomizeBuildList: [Kustomization]
|
||||
}
|
||||
}
|
||||
@@ -175,11 +191,81 @@ import (
|
||||
Resources: #Resources
|
||||
|
||||
// output represents the build plan provided to the holos cli.
|
||||
Output: v1.#BuildPlan & {
|
||||
Output: #BuildPlan & {
|
||||
_Name: Name
|
||||
_Namespace: Namespace
|
||||
// resources is a map unlike other build plans which use a list.
|
||||
spec: components: resources: "\(Name)": {
|
||||
metadata: name: Name
|
||||
apiObjectMap: (v1.#APIObjects & {apiObjects: Resources}).apiObjectMap
|
||||
metadata: name: Name
|
||||
metadata: namespace: Namespace
|
||||
apiObjectMap: (#APIObjects & {apiObjects: Resources}).apiObjectMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#BuildPlan: core.#BuildPlan & {
|
||||
_Name: string
|
||||
_Namespace?: string
|
||||
let NAME = "gitops/\(_Name)"
|
||||
|
||||
// Render the ArgoCD Application for GitOps.
|
||||
spec: components: resources: (NAME): {
|
||||
metadata: name: NAME
|
||||
if _Namespace != _|_ {
|
||||
metadata: namespace: _Namespace
|
||||
}
|
||||
|
||||
deployFiles: (#Argo & {ComponentName: _Name}).deployFiles
|
||||
}
|
||||
}
|
||||
|
||||
// #ArgoApplication represents an argocd Application resource for each
|
||||
// component, written using the #HolosComponent.deployFiles field.
|
||||
#Argo: {
|
||||
ComponentName: string
|
||||
|
||||
Application: app.#Application & {
|
||||
metadata: name: ComponentName
|
||||
metadata: namespace: "argocd"
|
||||
spec: {
|
||||
destination: server: "https://kubernetes.default.svc"
|
||||
project: "default"
|
||||
source: {
|
||||
path: "\(_Platform.Model.argocd.deployRoot)/deploy/clusters/\(_ClusterName)/components/\(ComponentName)"
|
||||
repoURL: _Platform.Model.argocd.repoURL
|
||||
targetRevision: _Platform.Model.argocd.targetRevision
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deployFiles represents the output files to write along side the component.
|
||||
deployFiles: "clusters/\(_ClusterName)/gitops/\(ComponentName).application.gen.yaml": yaml.Marshal(Application)
|
||||
}
|
||||
|
||||
// #APIObjects defines the output format for kubernetes api objects. The holos
|
||||
// cli expects the yaml representation of each api object in the apiObjectMap
|
||||
// field.
|
||||
#APIObjects: core.#APIObjects & {
|
||||
// apiObjects represents the un-marshalled form of each kubernetes api object
|
||||
// managed by a holos component.
|
||||
apiObjects: {
|
||||
[Kind=string]: {
|
||||
[string]: {
|
||||
kind: Kind
|
||||
...
|
||||
}
|
||||
}
|
||||
ConfigMap: [string]: corev1.#ConfigMap & {apiVersion: "v1"}
|
||||
}
|
||||
|
||||
// apiObjectMap holds the marshalled representation of apiObjects
|
||||
apiObjectMap: {
|
||||
for kind, v in apiObjects {
|
||||
"\(kind)": {
|
||||
for name, obj in v {
|
||||
"\(name)": yaml.Marshal(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ let Chart = {
|
||||
|
||||
// Refer to https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/
|
||||
let Policy = [
|
||||
"g, argocd-view, role:readonly",
|
||||
"g, prod-cluster-view, role:readonly",
|
||||
"g, prod-cluster-edit, role:readonly",
|
||||
"g, prod-cluster-admin, role:admin",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package holos
|
||||
|
||||
// _DBName is the database name used across multiple holos components in this project
|
||||
_DBName: "backstage"
|
||||
|
||||
_Component: {
|
||||
metadata: name: "backstage"
|
||||
metadata: namespace: "backstage"
|
||||
spec: hostname: "backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain)"
|
||||
spec: port: 7007
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package holos
|
||||
|
||||
import (
|
||||
is "cert-manager.io/issuer/v1"
|
||||
crt "cert-manager.io/certificate/v1"
|
||||
)
|
||||
|
||||
// Manage an Issuer for the database.
|
||||
|
||||
// Both cockroach and postgres handle tls database connections with cert manager
|
||||
// PGO: https://github.com/CrunchyData/postgres-operator-examples/tree/main/kustomize/certmanager/certman
|
||||
// CRDB: https://github.com/cockroachdb/helm-charts/blob/3dcf96726ebcfe3784afb526ddcf4095a1684aea/README.md?plain=1#L196-L201
|
||||
|
||||
// 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)
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
let SelfSigned = "\(_DBName)-selfsigned"
|
||||
let RootCA = "\(_DBName)-root-ca"
|
||||
let Orgs = ["Database"]
|
||||
|
||||
let Objects = {
|
||||
Name: "backstage-certs"
|
||||
Namespace: "backstage"
|
||||
|
||||
Resources: {
|
||||
// Put everything in the same namespace.
|
||||
[_]: {
|
||||
[NAME=_]: {
|
||||
metadata: name: NAME
|
||||
metadata: namespace: Namespace
|
||||
}
|
||||
}
|
||||
|
||||
Issuer: {
|
||||
"\(SelfSigned)": is.#Issuer & {
|
||||
_description: "Self signed issuer to issue ca certs"
|
||||
metadata: name: SelfSigned
|
||||
spec: selfSigned: {}
|
||||
}
|
||||
"\(RootCA)": is.#Issuer & {
|
||||
_description: "Root signed intermediate ca to issue mtls database certs"
|
||||
metadata: name: RootCA
|
||||
spec: ca: secretName: RootCA
|
||||
}
|
||||
}
|
||||
Certificate: {
|
||||
"\(RootCA)": crt.#Certificate & {
|
||||
_description: "Root CA cert for database"
|
||||
metadata: name: RootCA
|
||||
spec: {
|
||||
commonName: RootCA
|
||||
isCA: true
|
||||
issuerRef: group: "cert-manager.io"
|
||||
issuerRef: kind: "Issuer"
|
||||
issuerRef: name: SelfSigned
|
||||
privateKey: algorithm: "ECDSA"
|
||||
privateKey: size: 256
|
||||
secretName: RootCA
|
||||
subject: organizations: Orgs
|
||||
}
|
||||
}
|
||||
"\(_DBName)-primary-tls": #DatabaseCert & {
|
||||
// PGO managed name is "<cluster name>-cluster-cert" e.g. zitadel-cluster-cert
|
||||
spec: {
|
||||
commonName: "\(_DBName)-primary"
|
||||
dnsNames: [
|
||||
commonName,
|
||||
"\(commonName).\(Namespace)",
|
||||
"\(commonName).\(Namespace).svc",
|
||||
"\(commonName).\(Namespace).svc.cluster.local",
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
]
|
||||
usages: ["digital signature", "key encipherment"]
|
||||
}
|
||||
}
|
||||
"\(_DBName)-repl-tls": #DatabaseCert & {
|
||||
spec: {
|
||||
commonName: "_crunchyrepl"
|
||||
dnsNames: [commonName]
|
||||
usages: ["digital signature", "key encipherment"]
|
||||
}
|
||||
}
|
||||
"\(_DBName)-client-tls": #DatabaseCert & {
|
||||
spec: {
|
||||
commonName: "\(_DBName)-client"
|
||||
dnsNames: [commonName]
|
||||
usages: ["digital signature", "key encipherment"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#DatabaseCert: crt.#Certificate & {
|
||||
metadata: name: string
|
||||
metadata: namespace: string
|
||||
spec: {
|
||||
duration: "2160h" // 90d
|
||||
renewBefore: "360h" // 15d
|
||||
issuerRef: group: "cert-manager.io"
|
||||
issuerRef: kind: "Issuer"
|
||||
issuerRef: name: RootCA
|
||||
privateKey: algorithm: "ECDSA"
|
||||
privateKey: size: 256
|
||||
secretName: metadata.name
|
||||
subject: organizations: Orgs
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Database Certs
|
||||
|
||||
This component issues postgres certificates using certmanager. Apply this component to the management cluster.
|
||||
|
||||
The purpose is to define customTLSSecret and customReplicationTLSSecret to provide certs that allow the standby to authenticate to the primary. For this type of standby, you must use custom TLS.
|
||||
|
||||
Refer to the PGO [Streaming Standby](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/disaster-recovery#streaming-standby) tutorial.
|
||||
@@ -0,0 +1,112 @@
|
||||
package holos
|
||||
|
||||
// TODO This entire config should be removed for clarity. Only the production
|
||||
// config should be referenced by the Deployment.
|
||||
|
||||
_BackstageAppConfig: {
|
||||
app: {
|
||||
title: "Holos Portal"
|
||||
baseUrl: "${BASE_URL}"
|
||||
}
|
||||
|
||||
organization: name: "My Company"
|
||||
|
||||
backend: {
|
||||
// Used for enabling authentication, secret is shared by all backend plugins
|
||||
// See https://backstage.io/docs/auth/service-to-service-auth for
|
||||
// information on the format
|
||||
// auth:
|
||||
// keys:
|
||||
// - secret: ${BACKEND_SECRET}
|
||||
baseUrl: "${BASE_URL}"
|
||||
listen: port: 7007
|
||||
// Uncomment the following host directive to bind to specific interfaces
|
||||
// host: 127.0.0.1
|
||||
csp: {
|
||||
"connect-src": ["'self'", "http:", "https:"]
|
||||
}
|
||||
// Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
|
||||
// Default Helmet Content-Security-Policy values can be removed by setting the key to false
|
||||
cors: {
|
||||
origin: "${BASE_URL}"
|
||||
methods: ["GET", "HEAD", "PATCH", "POST", "PUT", "DELETE"]
|
||||
credentials: true
|
||||
}
|
||||
}
|
||||
// workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
|
||||
|
||||
integrations: {
|
||||
github: [{
|
||||
host: "github.com"
|
||||
// This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information
|
||||
// about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration
|
||||
token: "${GITHUB_TOKEN}"
|
||||
}]
|
||||
}
|
||||
//## Example for how to add your GitHub Enterprise instance using the API:
|
||||
// - host: ghe.example.net
|
||||
// apiBaseUrl: https://ghe.example.net/api/v3
|
||||
// token: ${GHE_TOKEN}
|
||||
|
||||
proxy: null
|
||||
//## Example for how to add a proxy endpoint for the frontend.
|
||||
//## A typical reason to do this is to handle HTTPS and CORS for internal services.
|
||||
// endpoints:
|
||||
// '/test':
|
||||
// target: 'https://example.com'
|
||||
// changeOrigin: true
|
||||
// Reference documentation http://backstage.io/docs/features/techdocs/configuration
|
||||
// Note: After experimenting with basic setup, use CI/CD to generate docs
|
||||
// and an external cloud storage when deploying TechDocs for production use-case.
|
||||
// https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach
|
||||
techdocs: {
|
||||
builder: "local" // Alternatives - 'external'
|
||||
generator: {
|
||||
runIn: "docker"
|
||||
} // Alternatives - 'local'
|
||||
publisher: {
|
||||
type: "local"
|
||||
}
|
||||
} // Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.
|
||||
|
||||
auth: {
|
||||
environment: "development"
|
||||
// see https://backstage.io/docs/auth/ to learn about auth providers
|
||||
providers: {
|
||||
// See https://backstage.io/docs/auth/guest/provider
|
||||
guest: {}
|
||||
}
|
||||
}
|
||||
|
||||
scaffolder: null
|
||||
// see https://backstage.io/docs/features/software-templates/configuration for software template options
|
||||
|
||||
catalog: {
|
||||
import: {
|
||||
entityFilename: "catalog-info.yaml"
|
||||
pullRequestBranchName: "backstage-integration"
|
||||
}
|
||||
rules: [{allow: ["Component", "System", "API", "Resource", "Location"]}]
|
||||
locations: [
|
||||
{
|
||||
// Local example data, file locations are relative to the backend process, typically `packages/backend`
|
||||
type: "file"
|
||||
target: "../../examples/entities.yaml"
|
||||
},
|
||||
{
|
||||
// Local example template
|
||||
type: "file"
|
||||
target: "../../examples/template/template.yaml"
|
||||
rules: [{
|
||||
allow: ["Template"]}]
|
||||
},
|
||||
{
|
||||
// Local organizational data
|
||||
type: "file"
|
||||
target: "../../org.yaml"
|
||||
rules: [{
|
||||
allow: ["User", "Group"]}]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package holos
|
||||
|
||||
// Imported from https://github.com/holos-run/portal/blob/d5127715fb4710b9b272768e6a1ce2ff122e693e/app-config.production.yaml
|
||||
|
||||
_BackstageProductionConfig: {
|
||||
app: {
|
||||
// Should be the same as backend.baseUrl when using the `app-backend` plugin.
|
||||
baseUrl: "${BASE_URL}"
|
||||
}
|
||||
|
||||
backend: {
|
||||
// Note that the baseUrl should be the URL that the browser and other clients
|
||||
// should use when communicating with the backend, i.e. it needs to be
|
||||
// reachable not just from within the backend host, but from all of your
|
||||
// callers. When its value is "http://localhost:7007", it's strictly private
|
||||
// and can't be reached by others.
|
||||
baseUrl: "${BASE_URL}"
|
||||
// The listener can also be expressed as a single <host>:<port> string. In this case we bind to
|
||||
// all interfaces, the most permissive setting. The right value depends on your specific deployment.
|
||||
listen: ":7007"
|
||||
|
||||
// config options: https://node-postgres.com/api/client
|
||||
database: {
|
||||
client: "pg"
|
||||
connection: {
|
||||
host: "${POSTGRES_HOST}"
|
||||
port: "${POSTGRES_PORT}"
|
||||
user: "${POSTGRES_USER}"
|
||||
password: "${POSTGRES_PASSWORD}"
|
||||
ssl: ca: "${PGBOUNCER_CA_ROOT}"
|
||||
}
|
||||
}
|
||||
|
||||
reading: allow: [{
|
||||
host: "holos.run"
|
||||
}, {
|
||||
host: "*.holos.run"
|
||||
}, {
|
||||
host: "openinfrastructure.co"
|
||||
}, {
|
||||
host: "*.openinfrastructure.co"
|
||||
}]
|
||||
}
|
||||
|
||||
auth: {
|
||||
environment: "production"
|
||||
providers: {
|
||||
guest: null
|
||||
holosProxy: {
|
||||
issuer: "https://login.holos.run"
|
||||
audience: "269746002573969304"
|
||||
oidcIdTokenHeader: "x-oidc-id-token"
|
||||
signIn: resolvers: [{
|
||||
resolver: "emailMatchingUserEntityProfileEmail"
|
||||
}, {
|
||||
resolver: "signInWithoutCatalogUser"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catalog: {
|
||||
// Overrides the default list locations from app-config.yaml
|
||||
// Refer to https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog
|
||||
//
|
||||
// NOTE: In production, CWD is /app This is different than development where
|
||||
// CWD is ./packages/backend, As a result, entries cannot be copied verbatim
|
||||
// from app-config.yaml otherwise they will not resolve to the correct location.
|
||||
locations: [{
|
||||
// Initial iam User and Group data is expected to be provided in the Secret.
|
||||
// Local example data, file locations are relative to the backend process, typically `packages/backend`
|
||||
type: "file"
|
||||
target: "/config/iam.yaml"
|
||||
rules: [{
|
||||
allow: ["User", "Group"]}]
|
||||
}]
|
||||
|
||||
// GitHub Discovery
|
||||
// Refer to https://backstage.io/docs/integrations/github/discovery/#configuration
|
||||
providers: {
|
||||
github: {
|
||||
primaryOrg: {
|
||||
organization: string & _Platform.Model.github.primaryOrg
|
||||
catalogPath: "/catalog-info.yaml"
|
||||
filters: {
|
||||
branch: "main"
|
||||
repository: ".*" // Regex
|
||||
}
|
||||
// same options as in TaskScheduleDefinition
|
||||
schedule: {
|
||||
// supports cron, ISO duration, "human duration" as used in code
|
||||
frequency: minutes: 30
|
||||
// supports ISO duration, "human duration" as used in code
|
||||
timeout: minutes: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refers to ExternalSecret github-app-credentials. See the readme.md for how this secret is produced.
|
||||
integrations: {
|
||||
github: [{
|
||||
host: "github.com"
|
||||
apps: [{"$include": "/secrets/github-app-credentials/github-app-credentials.yaml"}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
let ContainerPort = _Component.spec.port
|
||||
|
||||
let Objects = {
|
||||
Name: "\(_Component.metadata.name)-backend"
|
||||
Namespace: _Component.metadata.namespace
|
||||
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
let MatchLabels = {
|
||||
"app.kubernetes.io/name": _Component.metadata.name
|
||||
"app.kubernetes.io/instance": _Component.metadata.name
|
||||
"app.kubernetes.io/component": Name
|
||||
}
|
||||
|
||||
Resources: {
|
||||
// Grant the Gateway ns the ability to refer to the Service from HTTPRoutes.
|
||||
ReferenceGrant: (#IstioGatewaysNamespace): #ReferenceGrant
|
||||
|
||||
// For the Github integration.
|
||||
ExternalSecret: [_]: #ExternalSecret & {metadata: namespace: Namespace}
|
||||
ExternalSecret: githubAppCredentials: metadata: name: "github-app-credentials"
|
||||
|
||||
// Primary configuration for backstage to pull unified config data.
|
||||
ConfigMap: config: {
|
||||
metadata: namespace: Namespace
|
||||
metadata: name: Name
|
||||
data: {
|
||||
"app-config.yaml": yaml.Marshal(_BackstageAppConfig)
|
||||
"app-config.production.yaml": yaml.Marshal(_BackstageProductionConfig)
|
||||
"iam.yaml": yaml.MarshalStream([for x in _BackstageIAMConfig {x}])
|
||||
}
|
||||
}
|
||||
|
||||
Deployment: backstage: {
|
||||
metadata: labels: MatchLabels
|
||||
spec: {
|
||||
selector: matchLabels: MatchLabels
|
||||
|
||||
template: {
|
||||
metadata: labels: "sidecar.istio.io/inject": "true"
|
||||
metadata: labels: MatchLabels
|
||||
spec: {
|
||||
securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
serviceAccountName: "default"
|
||||
containers: [{
|
||||
name: "backstage-backend"
|
||||
image: "quay.io/holos-run/portal:latest"
|
||||
imagePullPolicy: "Always"
|
||||
// https://github.com/backstage/backstage/blob/v1.27.6/packages/create-app/templates/default-app/packages/backend/Dockerfile#L52
|
||||
command: [
|
||||
"node",
|
||||
"packages/backend",
|
||||
"--config",
|
||||
"/config/app-config.yaml",
|
||||
"--config",
|
||||
"/config/app-config.production.yaml",
|
||||
]
|
||||
// Refer to https://backstage.io/docs/conf/writing#environment-variable-overrides
|
||||
//
|
||||
// Individual configuration values can be overridden using
|
||||
// environment variables prefixed with APP_CONFIG_. Everything
|
||||
// following that prefix in the environment variable name will be
|
||||
// used as the config key, with _ replaced by .. For example, to
|
||||
// override the app.baseUrl value, set the APP_CONFIG_app_baseUrl
|
||||
// environment variable to the desired value.
|
||||
//
|
||||
// The value of the environment variable is parsed as JSON, but it will fall back
|
||||
// to being interpreted as a string if it fails to parse. Note that if you for
|
||||
// example want to pass on the string "false", you need to wrap it in double
|
||||
// quotes, e.g. export APP_CONFIG_example='"false"'.
|
||||
env: [
|
||||
{
|
||||
name: "BASE_URL"
|
||||
value: "https://" + _Component.spec.hostname
|
||||
},
|
||||
{
|
||||
name: "ORG_DOMAIN"
|
||||
value: _Platform.Model.org.domain
|
||||
},
|
||||
{
|
||||
name: "POSTGRES_HOST"
|
||||
valueFrom: secretKeyRef: {
|
||||
name: "\(_DBName)-pguser-\(_DBName)-admin"
|
||||
key: "pgbouncer-host"
|
||||
optional: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "POSTGRES_PORT"
|
||||
valueFrom: secretKeyRef: {
|
||||
name: "\(_DBName)-pguser-\(_DBName)-admin"
|
||||
key: "pgbouncer-port"
|
||||
optional: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "POSTGRES_USER"
|
||||
valueFrom: secretKeyRef: {
|
||||
name: "\(_DBName)-pguser-\(_DBName)-admin"
|
||||
key: "user"
|
||||
optional: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "POSTGRES_PASSWORD"
|
||||
valueFrom: secretKeyRef: {
|
||||
name: "\(_DBName)-pguser-\(_DBName)-admin"
|
||||
key: "password"
|
||||
optional: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "PGBOUNCER_CA_ROOT"
|
||||
valueFrom: secretKeyRef: {
|
||||
name: "\(_DBName)-pgbouncer"
|
||||
key: "pgbouncer-frontend.ca-roots"
|
||||
optional: false
|
||||
}
|
||||
},
|
||||
]
|
||||
ports: [{
|
||||
name: "backend"
|
||||
containerPort: ContainerPort
|
||||
protocol: "TCP"
|
||||
}]
|
||||
volumeMounts: [
|
||||
{
|
||||
name: "config"
|
||||
mountPath: "/config"
|
||||
},
|
||||
{
|
||||
name: "github-app-credentials"
|
||||
mountPath: "/secrets/github-app-credentials"
|
||||
},
|
||||
]
|
||||
}]
|
||||
volumes: [
|
||||
{
|
||||
name: "config"
|
||||
configMap: name: ConfigMap.config.metadata.name
|
||||
},
|
||||
{
|
||||
name: "github-app-credentials"
|
||||
secret: secretName: ExternalSecret.githubAppCredentials.metadata.name
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Service: backstage: {
|
||||
metadata: labels: MatchLabels
|
||||
spec: {
|
||||
selector: MatchLabels
|
||||
_ports: http: {
|
||||
port: ContainerPort
|
||||
targetPort: ContainerPort
|
||||
protocol: "TCP"
|
||||
name: "http"
|
||||
}
|
||||
ports: [for x in _ports {x}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package holos
|
||||
|
||||
_BackstageIAMConfig: {
|
||||
groupAdmin: {
|
||||
// https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group
|
||||
apiVersion: "backstage.io/v1alpha1"
|
||||
kind: "Group"
|
||||
metadata: name: "prod-cluster-admin"
|
||||
spec: {
|
||||
type: "team"
|
||||
children: []
|
||||
}
|
||||
}
|
||||
|
||||
user1: {
|
||||
// https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
|
||||
apiVersion: "backstage.io/v1alpha1"
|
||||
kind: "User"
|
||||
metadata: name: "jeff"
|
||||
spec: {
|
||||
profile: email: "jeff@openinfrastructure.co"
|
||||
memberOf: ["prod-cluster-admin"]
|
||||
}
|
||||
}
|
||||
|
||||
user2: {
|
||||
// https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
|
||||
apiVersion: "backstage.io/v1alpha1"
|
||||
kind: "User"
|
||||
metadata: name: "gary"
|
||||
spec: {
|
||||
profile: email: "gary@openinfrastructure.co"
|
||||
memberOf: ["prod-cluster-admin"]
|
||||
}
|
||||
}
|
||||
|
||||
user3: {
|
||||
// https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
|
||||
apiVersion: "backstage.io/v1alpha1"
|
||||
kind: "User"
|
||||
metadata: name: "nate"
|
||||
spec: {
|
||||
profile: email: "nate@openinfrastructure.co"
|
||||
memberOf: ["prod-cluster-admin"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# Backstage Secrets
|
||||
|
||||
Backstage needs secrets in place on the management cluster to operate.
|
||||
|
||||
ExternalSecret `github-app-credentials` generated using:
|
||||
|
||||
Refer to the [portal](https://github.com/holos-run/portal) repo.
|
||||
|
||||
```sh
|
||||
# portal is your backstage repository created with `npx @backstage/create-app`
|
||||
cd portal
|
||||
|
||||
# my-org is your github organization.
|
||||
yarn backstage-cli create-github-app my-org
|
||||
|
||||
# Create the secret in your management cluster.
|
||||
mv github-app-backstage-*-credentials.yaml github-app-credentials.yaml
|
||||
holos create secret -n backstage --append-hash=false --from-file=github-app-credentials.yaml github-app-credentials
|
||||
|
||||
# Remove the secret from the local host.
|
||||
rm -f github-app-credentials.yaml
|
||||
```
|
||||
@@ -0,0 +1,188 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
// Restore from backup. Flip this to true after the database is provisioned and
|
||||
// a backup has been taken.
|
||||
let RestoreFromBackup = false
|
||||
|
||||
// The Secret containing the pgbackrest s3.conf file.
|
||||
let S3Secret = "pgbackrest"
|
||||
|
||||
let Cluster = _Clusters[_ClusterName]
|
||||
|
||||
let DatabaseUser = _DBName
|
||||
let DatabaseAdmin = "\(_DBName)-admin"
|
||||
|
||||
// This must be an external storage bucket for our architecture.
|
||||
let BucketRepoName = "repo2"
|
||||
|
||||
// Restore options. Set the timestamp to a known good point in time.
|
||||
// time="2024-03-11T17:08:58Z" level=info msg="crunchy-pgbackrest ends"
|
||||
// let RestoreOptions = ["--type=time", "--target=\"2024-03-11 17:10:00+00\""]
|
||||
|
||||
// Restore the most recent backup.
|
||||
let RestoreOptions = []
|
||||
|
||||
let Objects = {
|
||||
Name: "backstage-database"
|
||||
Namespace: "backstage"
|
||||
|
||||
Resources: {
|
||||
// All resources go into the same namespace
|
||||
[_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
PostgresCluster: db: HighlyAvailable & {
|
||||
metadata: name: _DBName
|
||||
spec: {
|
||||
image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0"
|
||||
postgresVersion: 16
|
||||
// Custom certs are necessary for streaming standby replication which we use to replicate between two regions.
|
||||
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/disaster-recovery#streaming-standby
|
||||
customTLSSecret: name: "\(_DBName)-primary-tls"
|
||||
customReplicationTLSSecret: name: "\(_DBName)-repl-tls"
|
||||
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/references/crd/5.5.x/postgrescluster#postgresclusterspecusersindex
|
||||
users: [
|
||||
{name: DatabaseUser},
|
||||
// NOTE: Users with SUPERUSER role cannot log in through pgbouncer. Use options that allow zitadel admin to use pgbouncer.
|
||||
// Refer to: https://github.com/CrunchyData/postgres-operator/issues/3095#issuecomment-1904712211
|
||||
{name: DatabaseAdmin, options: "CREATEDB CREATEROLE", databases: [_DBName, "postgres"]},
|
||||
]
|
||||
users: [...{databases: [_DBName, ...]}]
|
||||
instances: [{
|
||||
replicas: 2
|
||||
dataVolumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: "20Gi"
|
||||
}
|
||||
}]
|
||||
standby: {
|
||||
repoName: BucketRepoName
|
||||
if Cluster.primary {
|
||||
enabled: false
|
||||
}
|
||||
if !Cluster.primary {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
// Monitoring configuration
|
||||
monitoring: pgmonitor: exporter: image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres-exporter:ubi8-5.5.1-0"
|
||||
// Restore from backup if and only if the cluster is primary and
|
||||
// RestoreFromBackup has transitioned from false to true.
|
||||
if Cluster.primary && RestoreFromBackup {
|
||||
dataSource: pgbackrest: {
|
||||
stanza: "db"
|
||||
configuration: backups.pgbackrest.configuration
|
||||
// Restore from known good full backup taken
|
||||
options: RestoreOptions
|
||||
global: {
|
||||
"\(BucketRepoName)-path": "/pgbackrest/\(metadata.namespace)/\(metadata.name)/\(BucketRepoName)"
|
||||
"\(BucketRepoName)-cipher-type": "aes-256-cbc"
|
||||
}
|
||||
repo: {
|
||||
name: BucketRepoName
|
||||
s3: backups.pgbackrest.repos[1].s3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups
|
||||
backups: pgbackrest: {
|
||||
configuration: [{secret: name: S3Secret}]
|
||||
// Defines details for manual pgBackRest backup Jobs
|
||||
manual: {
|
||||
// Note: the repoName value must match the config keys in the S3Secret.
|
||||
// This must be an external repository for backup / restore / regional failovers.
|
||||
repoName: BucketRepoName
|
||||
options: ["--type=full", ...]
|
||||
}
|
||||
// Defines details for performing an in-place restore using pgBackRest
|
||||
restore: {
|
||||
// Enables triggering a restore by annotating the postgrescluster with postgres-operator.crunchydata.com/pgbackrest-restore="$(date)"
|
||||
enabled: true
|
||||
repoName: BucketRepoName
|
||||
}
|
||||
global: {
|
||||
// Store only one full backup in the PV because it's more expensive than object storage.
|
||||
"\(repos[0].name)-retention-full": "1"
|
||||
// Store 14 days of full backups in the bucket.
|
||||
"\(BucketRepoName)-retention-full": string | *"14"
|
||||
"\(BucketRepoName)-retention-full-type": "count" | *"time" // time in days
|
||||
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups#encryption
|
||||
"\(BucketRepoName)-cipher-type": "aes-256-cbc"
|
||||
// "The convention we recommend for setting this variable is /pgbackrest/$NAMESPACE/$CLUSTER_NAME/repoN"
|
||||
// Ref: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups#understanding-backup-configuration-and-basic-operations
|
||||
"\(BucketRepoName)-path": "/pgbackrest/\(metadata.namespace)/\(metadata.name)/\(manual.repoName)"
|
||||
}
|
||||
repos: [
|
||||
{
|
||||
name: "repo1"
|
||||
volume: volumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: string | *"4Gi"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: BucketRepoName
|
||||
// Full backup weekly on Sunday at 1am, differntial daily at 1am every day except Sunday.
|
||||
schedules: full: string | *"0 1 * * 0"
|
||||
schedules: differential: string | *"0 1 * * 1-6"
|
||||
s3: {
|
||||
bucket: _BackupBucket.metadata.name
|
||||
region: _BackupBucket.spec.region
|
||||
endpoint: "s3.dualstack.\(region).amazonaws.com"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refer to https://github.com/holos-run/postgres-operator-examples/blob/main/kustomize/high-availability/ha-postgres.yaml
|
||||
let HighlyAvailable = {
|
||||
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
|
||||
kind: "PostgresCluster"
|
||||
metadata: name: string
|
||||
spec: {
|
||||
image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0"
|
||||
postgresVersion: 16
|
||||
instances: [{
|
||||
name: "pgha1"
|
||||
replicas: 2
|
||||
dataVolumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: string | *"20Gi"
|
||||
}
|
||||
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
|
||||
weight: 1
|
||||
podAffinityTerm: {
|
||||
topologyKey: "topology.kubernetes.io/zone"
|
||||
labelSelector: matchLabels: {
|
||||
"postgres-operator.crunchydata.com/cluster": metadata.name
|
||||
"postgres-operator.crunchydata.com/instance-set": name
|
||||
}
|
||||
}
|
||||
}]
|
||||
}]
|
||||
backups: pgbackrest: {
|
||||
image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0"
|
||||
}
|
||||
proxy: pgBouncer: {
|
||||
image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.21-3"
|
||||
replicas: 2
|
||||
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
|
||||
weight: 1
|
||||
podAffinityTerm: {
|
||||
topologyKey: "topology.kubernetes.io/zone"
|
||||
labelSelector: matchLabels: {
|
||||
"postgres-operator.crunchydata.com/cluster": metadata.name
|
||||
"postgres-operator.crunchydata.com/role": "pgbouncer"
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
let Objects = {
|
||||
Name: "\(_Component.metadata.name)-routes"
|
||||
Namespace: #IstioGatewaysNamespace
|
||||
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
Resources: HTTPRoute: (_Component.metadata.name): {
|
||||
spec: hostnames: [_Component.spec.hostname]
|
||||
spec: parentRefs: [{
|
||||
name: "default"
|
||||
namespace: #IstioGatewaysNamespace
|
||||
}]
|
||||
spec: rules: [
|
||||
{
|
||||
matches: [{path: {type: "PathPrefix", value: "/"}}]
|
||||
backendRefs: [{
|
||||
name: _Component.metadata.name
|
||||
namespace: _Component.metadata.namespace
|
||||
port: _Component.spec.port
|
||||
}]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
let Objects = {
|
||||
Name: "backstage-secrets"
|
||||
Namespace: "backstage"
|
||||
|
||||
Resources: {
|
||||
ExternalSecret: [_]: #ExternalSecret & {metadata: namespace: Namespace}
|
||||
|
||||
ExternalSecret: "\(_DBName)-primary-tls": #ExternalCert
|
||||
ExternalSecret: "\(_DBName)-repl-tls": #ExternalCert
|
||||
ExternalSecret: "\(_DBName)-client-tls": #ExternalCert
|
||||
ExternalSecret: "\(_DBName)-root-ca": #ExternalCert
|
||||
|
||||
ExternalSecret: "pgbackrest": _
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package holos
|
||||
|
||||
import (
|
||||
cpv1 "pkg.crossplane.io/provider/v1"
|
||||
cpdrcv1beta1 "pkg.crossplane.io/deploymentruntimeconfig/v1beta1"
|
||||
cpfuncv1beta1 "pkg.crossplane.io/function/v1beta1"
|
||||
cpawspcv1beta1 "aws.upbound.io/providerconfig/v1beta1"
|
||||
)
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
|
||||
// https://github.com/crossplane/crossplane/releases
|
||||
let CrossplaneVersion = "1.16.0"
|
||||
|
||||
// https://github.com/crossplane-contrib/provider-upjet-aws/releases
|
||||
let AWSProviderVersion = "v1.5.0"
|
||||
|
||||
let Chart = {
|
||||
Name: "crossplane"
|
||||
Version: CrossplaneVersion
|
||||
Namespace: "crossplane-system"
|
||||
|
||||
Repo: name: "crossplane-stable"
|
||||
Repo: url: "https://charts.crossplane.io/stable"
|
||||
|
||||
Values: {
|
||||
podSecurityContextCrossplane: {
|
||||
runAsNonRoot: true
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
}
|
||||
securityContextCrossplane: capabilities: drop: ["ALL"]
|
||||
podSecurityContextRBACManager: {
|
||||
runAsNonRoot: true
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
}
|
||||
securityContextRBACManager: capabilities: drop: ["ALL"]
|
||||
}
|
||||
|
||||
Resources: {
|
||||
// This DeploymentRuntimeConfig adds annotations to the service accounts spun up for the AWS providers.
|
||||
// https://docs.crossplane.io/latest/concepts/providers/#configuring-metadata-of-runtime-resources
|
||||
//
|
||||
// Adding this SA annotation causes the EKS Pod Identity Webhook to inject
|
||||
// environment variables for AWS authentication as well as mount the AWS token
|
||||
// file to the provider pod that uses the SA. For example:
|
||||
//
|
||||
// Environment:
|
||||
// AWS_STS_REGIONAL_ENDPOINTS: regional
|
||||
// AWS_DEFAULT_REGION: us-east-1
|
||||
// AWS_REGION: us-east-1
|
||||
// AWS_ROLE_ARN: arn:aws:iam::271053619184:role/holos-crossplane
|
||||
// AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
|
||||
// Mounts:
|
||||
// /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
|
||||
//
|
||||
// Docs: https://docs.crossplane.io/latest/concepts/providers/#runtime-configuration
|
||||
DeploymentRuntimeConfig: "aws-irsa": cpdrcv1beta1.#DeploymentRuntimeConfig & {
|
||||
metadata: name: "aws-irsa"
|
||||
spec: serviceAccountTemplate: metadata: annotations: "eks.amazonaws.com/role-arn": "arn:aws:iam::\(_Platform.Model.aws.accountNumber):role/holos-crossplane"
|
||||
spec: deploymentTemplate: spec: template: spec: containers: [
|
||||
{
|
||||
name: "package-runtime"
|
||||
args: ["--enable-external-secret-stores"]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// https://marketplace.upbound.io/providers/upbound/provider-family-aws
|
||||
Provider: "upbound-provider-family-aws": cpv1.#Provider & {
|
||||
metadata: name: "upbound-provider-family-aws"
|
||||
spec: package: "xpkg.upbound.io/upbound/provider-family-aws:\(AWSProviderVersion)"
|
||||
// The provider-family-aws provider doesn't need the IRSA SA annotation.
|
||||
spec: runtimeConfigRef: name: "default"
|
||||
}
|
||||
// https://marketplace.upbound.io/providers/upbound/provider-aws-s3
|
||||
Provider: "provider-aws-s3": cpv1.#Provider & {
|
||||
metadata: name: "provider-aws-s3"
|
||||
spec: package: "xpkg.upbound.io/upbound/provider-aws-s3:\(AWSProviderVersion)"
|
||||
spec: runtimeConfigRef: name: "aws-irsa"
|
||||
}
|
||||
// https://marketplace.upbound.io/providers/upbound/provider-aws-rds
|
||||
Provider: "provider-aws-rds": cpv1.#Provider & {
|
||||
metadata: name: "provider-aws-rds"
|
||||
spec: package: "xpkg.upbound.io/upbound/provider-aws-rds:\(AWSProviderVersion)"
|
||||
spec: runtimeConfigRef: name: "aws-irsa"
|
||||
}
|
||||
|
||||
// The patch-and-transform function is used in Compositions.
|
||||
// https://github.com/crossplane-contrib/function-patch-and-transform/releases
|
||||
Function: "function-patch-and-transform": cpfuncv1beta1.#Function & {
|
||||
metadata: name: "function-patch-and-transform"
|
||||
spec: package: "xpkg.upbound.io/crossplane-contrib/function-patch-and-transform:v0.5.0"
|
||||
}
|
||||
|
||||
// By setting the ProviderConfig's name to "default", all resources with an
|
||||
// apiVersion of "aws.upbound.io/v1beta1" will use this ProviderConfig unless
|
||||
// otherwise specified with a providerConfigRef.
|
||||
ProviderConfig: default: cpawspcv1beta1.#ProviderConfig & {
|
||||
metadata: name: "default"
|
||||
spec: credentials: source: "IRSA"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubectl kustomize build plan.
|
||||
(#Kustomize & {Name: "crossplane_crds"}).Output
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
# Install the CRDs for Crossplane and the providers we use.
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: "crossplane-system"
|
||||
resources:
|
||||
- https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml
|
||||
- https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_providers.yaml
|
||||
- https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_functions.yaml
|
||||
- https://raw.githubusercontent.com/crossplane-contrib/provider-upjet-aws/v1.5.0/package/crds/aws.upbound.io_providerconfigs.yaml
|
||||
@@ -0,0 +1 @@
|
||||
package holos
|
||||
@@ -0,0 +1,206 @@
|
||||
package holos
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
)
|
||||
|
||||
let NAME = "ecr-creds-manager"
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
// The path Pod Identity uses.
|
||||
let MOUNT = "/var/run/secrets/eks.amazonaws.com/serviceaccount/"
|
||||
let AWS_ACCOUNT = _Platform.Model.aws.accountNumber
|
||||
let AWS_REGION = _Platform.Model.aws.primaryRegion
|
||||
let AWS_ROLE_ARN = "arn:aws:iam::\(AWS_ACCOUNT):role/\(NAME)"
|
||||
|
||||
let Objects = {
|
||||
Name: NAME
|
||||
Namespace: "holos-system"
|
||||
|
||||
Resources: {
|
||||
// Kubernetes ServiceAccount used by the Job.
|
||||
ServiceAccount: "\(Name)": corev1.#ServiceAccount & {
|
||||
metadata: {
|
||||
name: Name
|
||||
namespace: Namespace
|
||||
annotations: "holos.run/description": "Refreshes image pull credentials for use with AWS ECR."
|
||||
// annotations: "eks.amazonaws.com/role-arn": AWS_ROLE_ARN
|
||||
}
|
||||
}
|
||||
|
||||
// Job needs to read and write secrets across the cluster.
|
||||
ClusterRole: "\(Name)": rbacv1.#ClusterRole & {
|
||||
metadata: name: Name
|
||||
rules: [
|
||||
{
|
||||
apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["*"]
|
||||
},
|
||||
{
|
||||
apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["list"]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Bind the Role to the ServiceAccount for the Job.
|
||||
ClusterRoleBinding: "\(Name)": rbacv1.#ClusterRoleBinding & {
|
||||
metadata: name: Name
|
||||
roleRef: {
|
||||
apiGroup: "rbac.authorization.k8s.io"
|
||||
kind: "ClusterRole"
|
||||
name: Name
|
||||
}
|
||||
subjects: [
|
||||
{
|
||||
kind: "ServiceAccount"
|
||||
name: Name
|
||||
namespace: Namespace
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Make the CronJob and Job identical.
|
||||
let JobSpec = {
|
||||
serviceAccountName: Name
|
||||
restartPolicy: "OnFailure"
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
runAsNonRoot: true
|
||||
runAsUser: 8192 // app
|
||||
}
|
||||
nodeSelector: {
|
||||
"cloud.google.com/gke-spot": "true"
|
||||
"kubernetes.io/os": "linux"
|
||||
}
|
||||
containers: [
|
||||
{
|
||||
name: "toolkit"
|
||||
image: "quay.io/holos/toolkit:latest"
|
||||
securityContext: {
|
||||
capabilities: drop: ["ALL"]
|
||||
allowPrivilegeEscalation: false
|
||||
}
|
||||
command: ["/bin/bash"]
|
||||
args: ["/config/entrypoint"]
|
||||
env: [
|
||||
{
|
||||
name: "HOME"
|
||||
value: "/tmp"
|
||||
},
|
||||
{
|
||||
name: "AWS_DEFAULT_REGION"
|
||||
value: AWS_REGION
|
||||
},
|
||||
{
|
||||
name: "AWS_REGION"
|
||||
value: AWS_REGION
|
||||
},
|
||||
{
|
||||
name: "AWS_ROLE_ARN"
|
||||
value: AWS_ROLE_ARN
|
||||
},
|
||||
{
|
||||
name: "AWS_WEB_IDENTITY_TOKEN_FILE"
|
||||
value: MOUNT + "token"
|
||||
},
|
||||
{
|
||||
name: "AWS_STS_REGIONAL_ENDPOINTS"
|
||||
value: "regional"
|
||||
},
|
||||
]
|
||||
volumeMounts: [
|
||||
{
|
||||
name: "config"
|
||||
mountPath: "/config"
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: "aws-token"
|
||||
mountPath: MOUNT
|
||||
readOnly: true
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
volumes: [
|
||||
{
|
||||
name: "config"
|
||||
configMap: name: Name
|
||||
},
|
||||
{
|
||||
name: "aws-token"
|
||||
projected: sources: [{
|
||||
serviceAccountToken: {
|
||||
path: "token"
|
||||
expirationSeconds: 3600
|
||||
audience: "sts.amazonaws.com"
|
||||
}
|
||||
}]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
Job: "\(Name)": batchv1.#Job & {
|
||||
metadata: {
|
||||
name: Name
|
||||
namespace: Namespace
|
||||
}
|
||||
spec: template: spec: JobSpec
|
||||
}
|
||||
|
||||
CronJob: "\(Name)": batchv1.#CronJob & {
|
||||
metadata: name: Name
|
||||
metadata: namespace: Namespace
|
||||
spec: {
|
||||
schedule: "0 */8 * * *"
|
||||
jobTemplate: spec: {
|
||||
template: spec: JobSpec
|
||||
backoffLimit: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfigMap: "\(Name)": corev1.#ConfigMap & {
|
||||
metadata: name: Name
|
||||
metadata: namespace: Namespace
|
||||
data: entrypoint: ENTRYPOINT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ENTRYPOINT = """
|
||||
#! /bin/bash
|
||||
#
|
||||
|
||||
tmpdir="$(mktemp -d)"
|
||||
finish() {
|
||||
rm -rf "${tmpdir}"
|
||||
}
|
||||
trap finish EXIT
|
||||
|
||||
set -xeuo pipefail
|
||||
|
||||
aws sts get-caller-identity
|
||||
|
||||
aws ecr get-login-password --region \(AWS_REGION) \\
|
||||
| docker login --username AWS --password-stdin \(AWS_ACCOUNT).dkr.ecr.\(AWS_REGION).amazonaws.com
|
||||
|
||||
kubectl create secret docker-registry ecr-creds-\(AWS_ACCOUNT) \\
|
||||
--from-file=.dockerconfigjson=${HOME}/.docker/config.json \\
|
||||
--dry-run=client -o yaml \\
|
||||
> "${tmpdir}/secret.yaml"
|
||||
|
||||
# Copy the secret to all namespaces
|
||||
for ns in $(kubectl -o=jsonpath='{.items[*].metadata.name}' get namespaces); do
|
||||
echo -n "Copying secret to namespace ${ns}: "
|
||||
kubectl -n $ns apply --server-side=true -f "${tmpdir}/secret.yaml" || continue
|
||||
echo "Usage: "kubectl -n $ns patch serviceaccount default -p "'"'{"imagePullSecrets": [{"name": "ecr-creds-\(AWS_ACCOUNT)"}]}'"'"
|
||||
done
|
||||
"""
|
||||
@@ -0,0 +1,33 @@
|
||||
# ECR Credentials Manager
|
||||
|
||||
This component manages a `ecr-creds-refresher` `CronJob` in the `holos-system` `Namespace` of the Management Cluster. This job authenticates to AWS using workload identity. Refer to [Use workload identity with AWS](https://cloud.google.com/kubernetes-engine/multi-cloud/docs/aws/how-to/use-workload-identity-aws) for information on how to configure AWS to accepts kubernetes service account tokens from the GKE Management Cluster.
|
||||
|
||||
Refer also to [Pod Identity](https://github.com/aws/amazon-eks-pod-identity-webhook?tab=readme-ov-file#eks-walkthrough)
|
||||
|
||||
> [!NOTE]
|
||||
> Both documents refer to EKS, but the process is the same and works on any kubernetes cluster.
|
||||
|
||||
Example [trust policy][trust-policy]:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Federated": "arn:aws:iam::637423192589:oidc-provider/container.googleapis.com/v1/projects/holos-ops/locations/us-central1/clusters/management"
|
||||
},
|
||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"container.googleapis.com/v1/projects/holos-ops/locations/us-central1/clusters/management:aud": "sts.amazonaws.com",
|
||||
"container.googleapis.com/v1/projects/holos-ops/locations/us-central1/clusters/management:sub": "system:serviceaccount:holos-system:ecr-creds-manager"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
[trust-policy]: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-logic-multiple-context-keys-or-values.html
|
||||
@@ -0,0 +1,20 @@
|
||||
package holos
|
||||
|
||||
let NAME = "ecr-creds-refresher"
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
|
||||
let AWS_ACCOUNT = _Platform.Model.aws.accountNumber
|
||||
|
||||
let Objects = {
|
||||
Name: NAME
|
||||
Namespace: "default"
|
||||
|
||||
for Namespace in _Namespaces {
|
||||
Resources: ExternalSecret: "\(Namespace.metadata.name)/ecr-creds-\(AWS_ACCOUNT)": #ExternalSecret & {
|
||||
metadata: name: "ecr-creds-\(AWS_ACCOUNT)"
|
||||
metadata: namespace: Namespace.metadata.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// This sets up the AWS EKS Pod Identity Webhook, which is used to inject AWS
|
||||
// credentials into pods so services can use IRSA for AWS authentication.
|
||||
//
|
||||
// See: https://github.com/aws/amazon-eks-pod-identity-webhook
|
||||
//
|
||||
// There isn't an official Helm chart for the EKS Pod Identity Webhook, so we
|
||||
// use https://github.com/jkroepke/helm-charts/tree/main/charts/amazon-eks-pod-identity-webhook
|
||||
// See: https://github.com/aws/amazon-eks-pod-identity-webhook/issues/4
|
||||
|
||||
package holos
|
||||
|
||||
// https://github.com/jkroepke/helm-charts/tree/main/charts/amazon-eks-pod-identity-webhook
|
||||
let ChartVersion = "2.1.3"
|
||||
|
||||
// https://github.com/aws/amazon-eks-pod-identity-webhook/releases
|
||||
// https://registry.hub.docker.com/r/amazon/amazon-eks-pod-identity-webhook/tags
|
||||
let AppVersion = "v0.5.4"
|
||||
|
||||
let Chart = {
|
||||
Name: "amazon-eks-pod-identity-webhook"
|
||||
Version: ChartVersion
|
||||
Namespace: "aws-pod-identity"
|
||||
|
||||
Repo: name: "jkroepke"
|
||||
Repo: url: "https://jkroepke.github.io/helm-charts"
|
||||
|
||||
Values: {
|
||||
image: tag: AppVersion
|
||||
config: {
|
||||
tokenAudience: "sts.amazonaws.com"
|
||||
defaultAwsRegion: _Platform.Model.aws.primaryRegion
|
||||
extraArgs: ["-v=4"] // verbosity of at least 4 is needed to see mutation events.
|
||||
}
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["ALL"]
|
||||
runAsNonRoot: true
|
||||
}
|
||||
podSecurityContext: {
|
||||
// https://github.com/aws/amazon-eks-pod-identity-webhook/blob/master/README.md#usage-with-non-root-container-user
|
||||
fsGroup: 2000
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
@@ -21,6 +21,16 @@ let Objects = {
|
||||
}
|
||||
}
|
||||
|
||||
// Manage a service account to prevent ArgoCD from pruning it.
|
||||
ServiceAccount: "default-istio": {
|
||||
metadata: namespace: Namespace
|
||||
metadata: labels: {
|
||||
"gateway.istio.io/managed": "istio.io-gateway-controller"
|
||||
"gateway.networking.k8s.io/gateway-name": "default"
|
||||
"istio.io/gateway-name": "default"
|
||||
}
|
||||
}
|
||||
|
||||
// The default gateway with all listeners attached to tls certs.
|
||||
Gateway: default: {
|
||||
metadata: {
|
||||
@@ -32,8 +42,10 @@ let Objects = {
|
||||
}
|
||||
spec: {
|
||||
// Work with a struct of listeners instead of a list.
|
||||
_listeners: (#WildcardListener & {Name: "admin", Selector: _Selector.GrantSubdomainAdmin}).Output
|
||||
_listeners: (#WildcardListener & {Name: "admin", Selector: _Selector.GrantSubdomainAdmin, Cluster: true}).Output
|
||||
_listeners: (#WildcardListener & {Name: "login", Selector: _Selector.GrantSubdomainLogin, Cluster: false}).Output
|
||||
_listeners: (#WildcardListener & {Name: "app", Selector: _Selector.GrantSubdomainApp, Cluster: false}).Output
|
||||
_listeners: (#WildcardListener & {Name: "app", Selector: _Selector.GrantSubdomainApp, Cluster: true}).Output
|
||||
listeners: [for x in _listeners {x}]
|
||||
}
|
||||
}
|
||||
@@ -46,16 +58,19 @@ let Objects = {
|
||||
Selector: matchLabels: {[string]: string}
|
||||
|
||||
_Hostname: string
|
||||
_Prefix: string
|
||||
if Cluster == true {
|
||||
_Hostname: "\(Name).\(_ClusterName).\(_Platform.Model.org.domain)"
|
||||
_Prefix: "region-\(Name)"
|
||||
}
|
||||
if Cluster == false {
|
||||
_Hostname: "\(Name).\(_Platform.Model.org.domain)"
|
||||
_Prefix: "global-\(Name)"
|
||||
}
|
||||
|
||||
Output: [NAME=string]: {name: NAME}
|
||||
Output: {
|
||||
"\(Name)-apex": {
|
||||
"\(_Prefix)-apex": {
|
||||
hostname: _Hostname
|
||||
port: 443
|
||||
protocol: "HTTPS"
|
||||
@@ -68,7 +83,7 @@ let Objects = {
|
||||
allowedRoutes: namespaces: from: "Selector"
|
||||
allowedRoutes: namespaces: selector: Selector
|
||||
}
|
||||
"\(Name)-prefix": {
|
||||
"\(_Prefix)-prefix": {
|
||||
hostname: "*.\(_Hostname)"
|
||||
port: 443
|
||||
protocol: "HTTPS"
|
||||
|
||||
@@ -10,8 +10,7 @@ let Objects = {
|
||||
// Constrain the metadata of all component resources.
|
||||
Resources: [_]: [_]: metadata: _HTTPBin.metadata
|
||||
|
||||
// Grant the Gateway namespace the ability to refer to the backend httpbin
|
||||
// service in HTTPRoutes.
|
||||
// Grant the Gateway ns the ability to refer to the Service from HTTPRoutes.
|
||||
Resources: ReferenceGrant: (#IstioGatewaysNamespace): #ReferenceGrant
|
||||
|
||||
Resources: {
|
||||
|
||||
@@ -7,17 +7,19 @@ let Objects = {
|
||||
Name: "authpolicy"
|
||||
Namespace: _AuthProxy.metadata.namespace
|
||||
|
||||
let Metadata = _IAP.metadata
|
||||
let Selector = {matchLabels: "istio.io/gateway-name": "default"}
|
||||
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
Resources: [_]: [NAME=string]: {
|
||||
metadata: _IAP.metadata
|
||||
metadata: name: NAME
|
||||
metadata: namespace: Namespace
|
||||
}
|
||||
|
||||
// Auth policy resources represent the RequestAuthentication and
|
||||
// AuthorizationPolicy resources in the istio-gateways namespace governing the
|
||||
// default Gateway.
|
||||
Resources: {
|
||||
RequestAuthentication: (Name): {
|
||||
metadata: Metadata & {name: Name}
|
||||
spec: jwtRules: [{
|
||||
audiences: ["\(_AuthProxy.projectID)"]
|
||||
forwardOriginalToken: true
|
||||
@@ -30,7 +32,6 @@ let Objects = {
|
||||
AuthorizationPolicy: "\(Name)-custom": {
|
||||
_description: "Route all requests through the auth proxy by default"
|
||||
|
||||
metadata: Metadata & {name: "\(Name)-custom"}
|
||||
spec: {
|
||||
action: "CUSTOM"
|
||||
provider: name: _AuthProxy.provider
|
||||
@@ -58,5 +59,220 @@ let Objects = {
|
||||
selector: Selector
|
||||
}
|
||||
}
|
||||
|
||||
AuthorizationPolicy: "\(Name)-allow-nothing": {
|
||||
_description: "Allow nothing"
|
||||
|
||||
spec: {
|
||||
action: "ALLOW"
|
||||
selector: Selector
|
||||
}
|
||||
}
|
||||
|
||||
AuthorizationPolicy: "\(Name)-allow-login": {
|
||||
_description: "Allow login"
|
||||
|
||||
spec: {
|
||||
action: "ALLOW"
|
||||
selector: Selector
|
||||
rules: [
|
||||
{
|
||||
to: [{
|
||||
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
|
||||
operation: hosts: [
|
||||
// Allow requests to the login service
|
||||
_AuthProxy.issuerHost,
|
||||
_AuthProxy.issuerHost + ":*",
|
||||
]
|
||||
}]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
AuthorizationPolicy: "\(Name)-allow-admin": {
|
||||
_description: "Allow cluster admin roles"
|
||||
|
||||
spec: {
|
||||
action: "ALLOW"
|
||||
selector: Selector
|
||||
rules: [
|
||||
{
|
||||
to: [{
|
||||
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
|
||||
operation: hosts: [
|
||||
// Allow authenticated users with cluster admin, edit, or view
|
||||
// roles to access admin interfaces.
|
||||
|
||||
// TODO(jeff): The set of admin services should be defined in a
|
||||
// nice root-level struct somewhere, probably as part of the
|
||||
// _Projects struct.
|
||||
"argocd.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
|
||||
"argocd.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
|
||||
"httpbin.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
|
||||
"httpbin.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
|
||||
"backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
|
||||
"backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
|
||||
]
|
||||
}]
|
||||
when: [
|
||||
// Must be issued by the platform identity provider.
|
||||
{
|
||||
key: "request.auth.principal"
|
||||
values: [_AuthProxy.issuerURL + "/*"]
|
||||
},
|
||||
// Must be intended for an app within the Holos Platform ZITADEL project.
|
||||
{
|
||||
key: "request.auth.audiences"
|
||||
values: [_AuthProxy.projectID]
|
||||
},
|
||||
// Must be presented by the istio ExtAuthz auth proxy.
|
||||
{
|
||||
key: "request.auth.presenter"
|
||||
values: [_AuthProxy.clientID]
|
||||
},
|
||||
// Must have one of the listed roles.
|
||||
AdminRoleGroups,
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
AuthorizationPolicy: "\(Name)-allow-holos-server": {
|
||||
_description: "Allow authenticated access to holos server"
|
||||
|
||||
spec: {
|
||||
action: "ALLOW"
|
||||
selector: Selector
|
||||
rules: [
|
||||
{
|
||||
to: [{
|
||||
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
|
||||
operation: hosts: [
|
||||
// Allow authenticated users with cluster admin, edit, or view
|
||||
// roles to access admin interfaces.
|
||||
|
||||
// TODO(jeff): The set of admin services should be defined in a
|
||||
// nice root-level struct somewhere, probably as part of the
|
||||
// _Projects struct.
|
||||
"app.\(_ClusterName).\(_Platform.Model.org.domain)",
|
||||
"app.\(_ClusterName).\(_Platform.Model.org.domain):*",
|
||||
"dev.app.\(_ClusterName).\(_Platform.Model.org.domain)",
|
||||
"dev.app.\(_ClusterName).\(_Platform.Model.org.domain):*",
|
||||
|
||||
"app.\(_Platform.Model.org.domain)",
|
||||
"app.\(_Platform.Model.org.domain):*",
|
||||
"dev.app.\(_Platform.Model.org.domain)",
|
||||
"dev.app.\(_Platform.Model.org.domain):*",
|
||||
]
|
||||
}]
|
||||
when: [
|
||||
// Must be issued by the platform identity provider.
|
||||
{
|
||||
key: "request.auth.principal"
|
||||
values: [_AuthProxy.issuerURL + "/*"]
|
||||
},
|
||||
// Must be intended for an app within the Holos Platform ZITADEL project.
|
||||
{
|
||||
key: "request.auth.audiences"
|
||||
values: [_AuthProxy.projectID]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
AuthorizationPolicy: "\(Name)-allow-portal": {
|
||||
_description: "Allow portal access"
|
||||
|
||||
spec: {
|
||||
action: "ALLOW"
|
||||
selector: Selector
|
||||
rules: [
|
||||
{
|
||||
to: [{
|
||||
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
|
||||
operation: hosts: [
|
||||
"backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
|
||||
"backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
|
||||
]
|
||||
}]
|
||||
when: [
|
||||
// Must be issued by the platform identity provider.
|
||||
{
|
||||
key: "request.auth.principal"
|
||||
values: [_AuthProxy.issuerURL + "/*"]
|
||||
},
|
||||
// Must be intended for an app within the Holos Platform ZITADEL project.
|
||||
{
|
||||
key: "request.auth.audiences"
|
||||
values: [_AuthProxy.projectID]
|
||||
},
|
||||
// Must be presented by the istio ExtAuthz auth proxy.
|
||||
{
|
||||
key: "request.auth.presenter"
|
||||
values: [_AuthProxy.clientID]
|
||||
},
|
||||
{
|
||||
key: "request.auth.claims[groups]"
|
||||
values: ["portal-view"]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
AuthorizationPolicy: "\(Name)-allow-argocd": {
|
||||
_description: "Allow portal access"
|
||||
|
||||
spec: {
|
||||
action: "ALLOW"
|
||||
selector: Selector
|
||||
rules: [
|
||||
{
|
||||
to: [{
|
||||
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
|
||||
operation: hosts: [
|
||||
"argocd.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
|
||||
"argocd.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
|
||||
]
|
||||
}]
|
||||
when: [
|
||||
// Must be issued by the platform identity provider.
|
||||
{
|
||||
key: "request.auth.principal"
|
||||
values: [_AuthProxy.issuerURL + "/*"]
|
||||
},
|
||||
// Must be intended for an app within the Holos Platform ZITADEL project.
|
||||
{
|
||||
key: "request.auth.audiences"
|
||||
values: [_AuthProxy.projectID]
|
||||
},
|
||||
// Must be presented by the istio ExtAuthz auth proxy.
|
||||
{
|
||||
key: "request.auth.presenter"
|
||||
values: [_AuthProxy.clientID]
|
||||
},
|
||||
{
|
||||
key: "request.auth.claims[groups]"
|
||||
values: ["argocd-view"]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let AdminRoleGroups = {
|
||||
key: "request.auth.claims[groups]"
|
||||
values: [
|
||||
"prod-cluster-admin",
|
||||
"prod-cluster-edit",
|
||||
"prod-cluster-view",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -33,10 +33,32 @@ let Objects = {
|
||||
metadata: ProxyMetadata
|
||||
data: "config.yaml": yaml.Marshal(AuthProxyConfig)
|
||||
let AuthProxyConfig = {
|
||||
injectResponseHeaders: [{
|
||||
name: _AuthProxy.idTokenHeader
|
||||
values: [{claim: "id_token"}]
|
||||
}]
|
||||
injectResponseHeaders: [
|
||||
{
|
||||
name: _AuthProxy.idTokenHeader
|
||||
values: [{claim: "id_token"}]
|
||||
},
|
||||
{
|
||||
name: "x-auth-request-email"
|
||||
values: [{claim: "email"}]
|
||||
},
|
||||
{
|
||||
name: "x-auth-request-groups"
|
||||
values: [{claim: "groups"}]
|
||||
},
|
||||
{
|
||||
name: "x-forwarded-email"
|
||||
values: [{claim: "email"}]
|
||||
},
|
||||
{
|
||||
name: "x-forwarded-user"
|
||||
values: [{claim: "email"}]
|
||||
},
|
||||
{
|
||||
name: "x-forwarded-preferred-username"
|
||||
values: [{claim: "preferred_username"}]
|
||||
},
|
||||
]
|
||||
providers: [{
|
||||
id: "Holos Platform"
|
||||
name: "Holos Platform"
|
||||
@@ -55,7 +77,7 @@ let Objects = {
|
||||
audienceClaims: ["aud"]
|
||||
emailClaim: "email"
|
||||
groupsClaim: "groups"
|
||||
userIDClaim: "sub"
|
||||
userIDClaim: "email"
|
||||
}
|
||||
}]
|
||||
server: BindAddress: ":\(_AuthProxy.servicePort)"
|
||||
|
||||
@@ -56,7 +56,7 @@ let Objects = {
|
||||
replicas: 2
|
||||
dataVolumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: "20Gi"
|
||||
resources: requests: storage: "50Gi"
|
||||
}
|
||||
}]
|
||||
standby: {
|
||||
@@ -122,7 +122,7 @@ let Objects = {
|
||||
name: "repo1"
|
||||
volume: volumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: string | *"4Gi"
|
||||
resources: requests: storage: string | *"50Gi"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -156,7 +156,7 @@ let HighlyAvailable = {
|
||||
replicas: 2
|
||||
dataVolumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: string | *"20Gi"
|
||||
resources: requests: storage: string | *"50Gi"
|
||||
}
|
||||
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
|
||||
weight: 1
|
||||
|
||||
@@ -3,7 +3,7 @@ package holos
|
||||
#Values: {
|
||||
// https://github.com/zitadel/zitadel/releases
|
||||
// Overrides the image tag whose default is the chart appVersion.
|
||||
image: tag: "v2.49.1"
|
||||
image: tag: "v2.54.1"
|
||||
|
||||
// Database credentials
|
||||
// Refer to https://access.crunchydata.com/documentation/postgres-operator/5.2.0/architecture/user-management/
|
||||
|
||||
@@ -63,6 +63,29 @@ let FormBuilder = v1.#FormBuilder & {
|
||||
}
|
||||
}
|
||||
|
||||
Sections: github: {
|
||||
displayName: "GitHub"
|
||||
description: "Configure the platform GitHub integration. These values are used by the Backstage component."
|
||||
|
||||
fieldConfigs: {
|
||||
primaryOrg: {
|
||||
type: "input"
|
||||
props: {
|
||||
label: "Organization"
|
||||
description: "Primary GitHub orgranization where code repositories reside. \(validation.messages.required)"
|
||||
pattern: "^[a-z]([a-z0-9]|-){\(minLength-2),\(maxLength-2)}[a-z]$"
|
||||
minLength: 2
|
||||
maxLength: 39
|
||||
required: true
|
||||
}
|
||||
validation: messages: {
|
||||
pattern: "It must be \(props.minLength) to \(props.maxLength) lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited. \(required)"
|
||||
required: "GitHub organization name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Sections: eso: {
|
||||
displayName: "Secret Store"
|
||||
description: "Configure the platform secret store. These values are used by the external-secrets-creds component. Note: this information is not sufficient to read secrets. To read secrets, the credential refresher job requires the workload clusters to be configured as workload identity providers."
|
||||
@@ -267,9 +290,76 @@ let FormBuilder = v1.#FormBuilder & {
|
||||
|
||||
Sections: argocd: {
|
||||
displayName: "ArgoCD"
|
||||
description: "Enter configuration values from the ArgoCD application in the Holos Platform project in ZITADEL."
|
||||
description: "Configure ArgoCD platform settings."
|
||||
|
||||
fieldConfigs: clientID: (#StandardFields & {DisplayName: displayName}).clientID
|
||||
fieldConfigs: {
|
||||
clientID: (#StandardFields & {DisplayName: displayName}).clientID
|
||||
|
||||
repoURL: {
|
||||
type: "input"
|
||||
props: {
|
||||
label: "Git repository URL"
|
||||
description: "Git repository URL, e.g. 'git@github.com:myorg/holos-infra.git'"
|
||||
minLength: 3
|
||||
maxLength: 128
|
||||
required: true
|
||||
}
|
||||
validation: messages: {
|
||||
minLength: "Must be at least \(props.minLength) characters"
|
||||
maxLength: "Must be at most \(props.maxLength) characters"
|
||||
}
|
||||
}
|
||||
|
||||
deployRoot: {
|
||||
type: "input"
|
||||
defaultValue: ""
|
||||
props: {
|
||||
label: "Deploy Root"
|
||||
description: "Path to the parent directory of the holos deploy directory."
|
||||
required: false
|
||||
}
|
||||
}
|
||||
targetRevision: {
|
||||
type: "input"
|
||||
defaultValue: "HEAD"
|
||||
props: {
|
||||
label: "Target Revision"
|
||||
description: "Git reference to deploy."
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Sections: aws: {
|
||||
displayName: "AWS"
|
||||
description: "Configure AWS settings."
|
||||
|
||||
fieldConfigs: {
|
||||
accountNumber: {
|
||||
type: "input"
|
||||
props: {
|
||||
label: "AWS Account Number"
|
||||
description: "AWS Account Number. \(validation.messages.required)"
|
||||
pattern: "^[0-9]+$"
|
||||
required: true
|
||||
}
|
||||
validation: messages: {
|
||||
pattern: "Must be a positive integer. \(required)"
|
||||
required: "aws sts get-caller-identity"
|
||||
}
|
||||
}
|
||||
primaryRegion: {
|
||||
type: "select"
|
||||
props: {
|
||||
label: "Primary Region"
|
||||
description: "Select the primary region for AWS resources."
|
||||
multiple: false
|
||||
options: AWSRegions
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,73 +385,73 @@ let FormBuilder = v1.#FormBuilder & {
|
||||
}
|
||||
|
||||
let GCPRegions = [
|
||||
{value: "africa-south1", label: "africa-south1"},
|
||||
{value: "asia-east1", label: "asia-east1"},
|
||||
{value: "asia-east2", label: "asia-east2"},
|
||||
{value: "asia-northeast1", label: "asia-northeast1"},
|
||||
{value: "asia-northeast2", label: "asia-northeast2"},
|
||||
{value: "asia-northeast3", label: "asia-northeast3"},
|
||||
{value: "asia-south1", label: "asia-south1"},
|
||||
{value: "asia-south2", label: "asia-south2"},
|
||||
{value: "asia-southeast1", label: "asia-southeast1"},
|
||||
{value: "asia-southeast2", label: "asia-southeast2"},
|
||||
{value: "australia-southeast1", label: "australia-southeast1"},
|
||||
{value: "australia-southeast2", label: "australia-southeast2"},
|
||||
{value: "europe-central2", label: "europe-central2"},
|
||||
{value: "europe-north1", label: "europe-north1"},
|
||||
{value: "europe-southwest1", label: "europe-southwest1"},
|
||||
{value: "europe-west1", label: "europe-west1"},
|
||||
{value: "europe-west10", label: "europe-west10"},
|
||||
{value: "europe-west12", label: "europe-west12"},
|
||||
{value: "europe-west2", label: "europe-west2"},
|
||||
{value: "europe-west3", label: "europe-west3"},
|
||||
{value: "europe-west4", label: "europe-west4"},
|
||||
{value: "europe-west6", label: "europe-west6"},
|
||||
{value: "europe-west8", label: "europe-west8"},
|
||||
{value: "europe-west9", label: "europe-west9"},
|
||||
{value: "me-central1", label: "me-central1"},
|
||||
{value: "me-central2", label: "me-central2"},
|
||||
{value: "me-west1", label: "me-west1"},
|
||||
{value: "africa-south1", label: "africa-south1"},
|
||||
{value: "asia-east1", label: "asia-east1"},
|
||||
{value: "asia-east2", label: "asia-east2"},
|
||||
{value: "asia-northeast1", label: "asia-northeast1"},
|
||||
{value: "asia-northeast2", label: "asia-northeast2"},
|
||||
{value: "asia-northeast3", label: "asia-northeast3"},
|
||||
{value: "asia-south1", label: "asia-south1"},
|
||||
{value: "asia-south2", label: "asia-south2"},
|
||||
{value: "asia-southeast1", label: "asia-southeast1"},
|
||||
{value: "asia-southeast2", label: "asia-southeast2"},
|
||||
{value: "australia-southeast1", label: "australia-southeast1"},
|
||||
{value: "australia-southeast2", label: "australia-southeast2"},
|
||||
{value: "europe-central2", label: "europe-central2"},
|
||||
{value: "europe-north1", label: "europe-north1"},
|
||||
{value: "europe-southwest1", label: "europe-southwest1"},
|
||||
{value: "europe-west1", label: "europe-west1"},
|
||||
{value: "europe-west10", label: "europe-west10"},
|
||||
{value: "europe-west12", label: "europe-west12"},
|
||||
{value: "europe-west2", label: "europe-west2"},
|
||||
{value: "europe-west3", label: "europe-west3"},
|
||||
{value: "europe-west4", label: "europe-west4"},
|
||||
{value: "europe-west6", label: "europe-west6"},
|
||||
{value: "europe-west8", label: "europe-west8"},
|
||||
{value: "europe-west9", label: "europe-west9"},
|
||||
{value: "me-central1", label: "me-central1"},
|
||||
{value: "me-central2", label: "me-central2"},
|
||||
{value: "me-west1", label: "me-west1"},
|
||||
{value: "northamerica-northeast1", label: "northamerica-northeast1"},
|
||||
{value: "northamerica-northeast2", label: "northamerica-northeast2"},
|
||||
{value: "southamerica-east1", label: "southamerica-east1"},
|
||||
{value: "southamerica-west1", label: "southamerica-west1"},
|
||||
{value: "us-central1", label: "us-central1"},
|
||||
{value: "us-east1", label: "us-east1"},
|
||||
{value: "us-east4", label: "us-east4"},
|
||||
{value: "us-east5", label: "us-east5"},
|
||||
{value: "us-south1", label: "us-south1"},
|
||||
{value: "us-west1", label: "us-west1"},
|
||||
{value: "us-west2", label: "us-west2"},
|
||||
{value: "us-west3", label: "us-west3"},
|
||||
{value: "us-west4", label: "us-west4"},
|
||||
{value: "southamerica-east1", label: "southamerica-east1"},
|
||||
{value: "southamerica-west1", label: "southamerica-west1"},
|
||||
{value: "us-central1", label: "us-central1"},
|
||||
{value: "us-east1", label: "us-east1"},
|
||||
{value: "us-east4", label: "us-east4"},
|
||||
{value: "us-east5", label: "us-east5"},
|
||||
{value: "us-south1", label: "us-south1"},
|
||||
{value: "us-west1", label: "us-west1"},
|
||||
{value: "us-west2", label: "us-west2"},
|
||||
{value: "us-west3", label: "us-west3"},
|
||||
{value: "us-west4", label: "us-west4"},
|
||||
]
|
||||
|
||||
let AWSRegions = [
|
||||
{value: "us-east-1", label: "N. Virginia (us-east-1)"},
|
||||
{value: "us-east-2", label: "Ohio (us-east-2)"},
|
||||
{value: "us-west-1", label: "N. California (us-west-1)"},
|
||||
{value: "us-west-2", label: "Oregon (us-west-2)"},
|
||||
{value: "us-gov-west1", label: "US GovCloud West (us-gov-west1)"},
|
||||
{value: "us-gov-east1", label: "US GovCloud East (us-gov-east1)"},
|
||||
{value: "ca-central-1", label: "Canada (ca-central-1)"},
|
||||
{value: "eu-north-1", label: "Stockholm (eu-north-1)"},
|
||||
{value: "eu-west-1", label: "Ireland (eu-west-1)"},
|
||||
{value: "eu-west-2", label: "London (eu-west-2)"},
|
||||
{value: "eu-west-3", label: "Paris (eu-west-3)"},
|
||||
{value: "eu-central-1", label: "Frankfurt (eu-central-1)"},
|
||||
{value: "eu-south-1", label: "Milan (eu-south-1)"},
|
||||
{value: "af-south-1", label: "Cape Town (af-south-1)"},
|
||||
{value: "us-east-1", label: "N. Virginia (us-east-1)"},
|
||||
{value: "us-east-2", label: "Ohio (us-east-2)"},
|
||||
{value: "us-west-1", label: "N. California (us-west-1)"},
|
||||
{value: "us-west-2", label: "Oregon (us-west-2)"},
|
||||
{value: "us-gov-west1", label: "US GovCloud West (us-gov-west1)"},
|
||||
{value: "us-gov-east1", label: "US GovCloud East (us-gov-east1)"},
|
||||
{value: "ca-central-1", label: "Canada (ca-central-1)"},
|
||||
{value: "eu-north-1", label: "Stockholm (eu-north-1)"},
|
||||
{value: "eu-west-1", label: "Ireland (eu-west-1)"},
|
||||
{value: "eu-west-2", label: "London (eu-west-2)"},
|
||||
{value: "eu-west-3", label: "Paris (eu-west-3)"},
|
||||
{value: "eu-central-1", label: "Frankfurt (eu-central-1)"},
|
||||
{value: "eu-south-1", label: "Milan (eu-south-1)"},
|
||||
{value: "af-south-1", label: "Cape Town (af-south-1)"},
|
||||
{value: "ap-northeast-1", label: "Tokyo (ap-northeast-1)"},
|
||||
{value: "ap-northeast-2", label: "Seoul (ap-northeast-2)"},
|
||||
{value: "ap-northeast-3", label: "Osaka (ap-northeast-3)"},
|
||||
{value: "ap-southeast-1", label: "Singapore (ap-southeast-1)"},
|
||||
{value: "ap-southeast-2", label: "Sydney (ap-southeast-2)"},
|
||||
{value: "ap-east-1", label: "Hong Kong (ap-east-1)"},
|
||||
{value: "ap-south-1", label: "Mumbai (ap-south-1)"},
|
||||
{value: "me-south-1", label: "Bahrain (me-south-1)"},
|
||||
{value: "sa-east-1", label: "São Paulo (sa-east-1)"},
|
||||
{value: "cn-north-1", label: "Bejing (cn-north-1)"},
|
||||
{value: "ap-east-1", label: "Hong Kong (ap-east-1)"},
|
||||
{value: "ap-south-1", label: "Mumbai (ap-south-1)"},
|
||||
{value: "me-south-1", label: "Bahrain (me-south-1)"},
|
||||
{value: "sa-east-1", label: "São Paulo (sa-east-1)"},
|
||||
{value: "cn-north-1", label: "Bejing (cn-north-1)"},
|
||||
{value: "cn-northwest-1", label: "Ningxia (cn-northwest-1)"},
|
||||
{value: "ap-southeast-3", label: "Jakarta (ap-southeast-3)"},
|
||||
]
|
||||
|
||||
@@ -40,6 +40,13 @@ package holos
|
||||
"authorization",
|
||||
"path",
|
||||
_AuthProxy.idTokenHeader,
|
||||
"x-forwaded-access-token",
|
||||
// For Backstage oauth2-proxy auth provider
|
||||
"x-forwarded-email",
|
||||
"x-forwarded-user",
|
||||
"x-forwarded-preferred-username",
|
||||
"x-auth-request-email",
|
||||
"x-auth-request-groups",
|
||||
]
|
||||
includeAdditionalHeadersInCheck: "X-Auth-Request-Redirect": "%REQ(x-forwarded-proto)%://%REQ(:authority)%%REQ(:path)%%REQ(:query)%"
|
||||
includeRequestHeadersInCheck: [
|
||||
|
||||
@@ -29,6 +29,7 @@ _Projects: {
|
||||
// Admin projects accessible at *.admin.<cluster>.<org.domain>
|
||||
holos: spec: namespaces: "holos-system": _
|
||||
argocd: spec: namespaces: argocd: _
|
||||
backstage: spec: namespaces: backstage: _
|
||||
|
||||
// Sync secrets from the management cluster to workload clusters.
|
||||
"external-secrets": spec: namespaces: "external-secrets": _
|
||||
@@ -62,24 +63,45 @@ _Projects: {
|
||||
spec: commonName: "*." + Subdomain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manage certificates for admin services in workload clusters.
|
||||
for Cluster in _Fleets.workload.clusters {
|
||||
// Issue a wildcard cert for all admin interfaces. We need to verify this is
|
||||
// well-behaved with Istio and HTTP2.
|
||||
let Subdomain = "admin.\(Cluster.name).\(_Platform.Model.org.domain)"
|
||||
_Projects: holos: spec: {
|
||||
// Crossplane
|
||||
crossplane: spec: namespaces: {
|
||||
"aws-pod-identity": _
|
||||
"crossplane-system": _
|
||||
}
|
||||
|
||||
holosapp: spec: {
|
||||
namespaces: "dev-holos": _
|
||||
namespaces: "prod-holos": _
|
||||
namespaces: "jeff-holos": _
|
||||
|
||||
let Subdomain = "app.\(_Platform.Model.org.domain)"
|
||||
certificates: "\(Subdomain)": #IngressCertificate
|
||||
certificates: "any.\(Subdomain)": #IngressCertificate & {
|
||||
spec: commonName: "*." + Subdomain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Issue a dedicated cert for argocd. This may be removed if the wildcard
|
||||
// works with the Gateway API.
|
||||
let Name = "argocd.\(Subdomain)"
|
||||
_Projects: argocd: spec: certificates: "\(Name)": #IngressCertificate & {metadata: name: Name}
|
||||
// Manage per-cluster certificates for services in workload clusters.
|
||||
for Cluster in _Fleets.workload.clusters {
|
||||
// Issue a wildcard cert for all admin interfaces. We need to verify this is
|
||||
// well-behaved with Istio and HTTP2.
|
||||
let CertPair = #ClusterCertPair & {cluster: Cluster.name}
|
||||
_Projects: holos: spec: (CertPair & {name: "admin"}).spec
|
||||
|
||||
// Holos app certs
|
||||
_Projects: holosapp: spec: (CertPair & {name: "app"}).spec
|
||||
}
|
||||
|
||||
#ClusterCertPair: {
|
||||
name: string
|
||||
cluster: string
|
||||
let Subdomain = name + ".\(cluster).\(_Platform.Model.org.domain)"
|
||||
spec: certificates: (Subdomain): #IngressCertificate
|
||||
spec: certificates: "any.\(Subdomain)": #IngressCertificate & {
|
||||
spec: commonName: "*." + Subdomain
|
||||
}
|
||||
}
|
||||
|
||||
// Platform components to manage.
|
||||
@@ -118,6 +140,28 @@ _Platform: Components: {
|
||||
path: "components/login/zitadel-certs"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
// ECR Credentials (ecr-creds-<account-number>)
|
||||
"\(Cluster.name)/ecr-creds-manager": {
|
||||
path: "components/ecr-creds-manager"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/eks-pod-identity-webhook": {
|
||||
path: "components/eks-pod-identity-webhook"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/crossplane_crds": {
|
||||
path: "components/crossplane/crds"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/crossplane": {
|
||||
path: "components/crossplane/controller"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
// Backstage certs
|
||||
"\(Cluster.name)/backstage-certs": {
|
||||
path: "components/backstage/management/certs"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
|
||||
// Components to manage on workload clusters.
|
||||
@@ -134,6 +178,14 @@ _Platform: Components: {
|
||||
path: "components/secretstores"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
// Secret ecr-creds-<aws-account-number> in each Namespace to pull images
|
||||
// from the private ECR registry.
|
||||
"\(Cluster.name)/ecr-creds-refresher": {
|
||||
path: "components/ecr-creds-refresher"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
// We use HTTPRoute from the Kubernetes Gateway API v1 instead of
|
||||
// VirtualService from the Istio Gateway API.
|
||||
"\(Cluster.name)/gateway-api": {
|
||||
path: "components/gateway-api"
|
||||
cluster: Cluster.name
|
||||
@@ -213,6 +265,34 @@ _Platform: Components: {
|
||||
path: "components/argo/creds"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
|
||||
// Holos server
|
||||
"\(Cluster.name)/apps/dev-holos-infra": {
|
||||
path: "apps/dev/holos/infra"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/apps/dev-holos-app": {
|
||||
path: "apps/dev/holos/app"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
|
||||
// Backstage
|
||||
"\(Cluster.name)/backstage-secrets": {
|
||||
path: "components/backstage/workload/secrets"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/backstage-database": {
|
||||
path: "components/backstage/workload/database"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/backstage-backend": {
|
||||
path: "components/backstage/workload/backend"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/backstage-routes": {
|
||||
path: "components/backstage/workload/routes"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,3 +346,8 @@ _AuthProxy: {
|
||||
// provider is the istio meshconfig extauthz provider of the authproxy
|
||||
provider: "default-gateway-authproxy"
|
||||
}
|
||||
|
||||
_BackupBucket: {
|
||||
metadata: name: _Platform.Model.zitadel.backupBucketName
|
||||
spec: region: _Platform.Model.zitadel.backupBucketRegion
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package holos
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
v1 "github.com/holos-run/holos/api/v1alpha1"
|
||||
core "github.com/holos-run/holos/api/core/v1alpha2"
|
||||
dto "github.com/holos-run/holos/service/gen/holos/object/v1alpha1:object"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
certv1 "cert-manager.io/certificate/v1"
|
||||
@@ -50,12 +50,12 @@ _Platform: #Platform & {
|
||||
Name: string | *"holos"
|
||||
|
||||
// Components represent the platform components to render.
|
||||
Components: [string]: v1.#PlatformSpecComponent
|
||||
Components: [string]: core.#PlatformSpecComponent
|
||||
|
||||
// Model represents the platform model from the web app form.
|
||||
Model: dto.#PlatformConfig.platform_model
|
||||
|
||||
Output: v1.#Platform & {
|
||||
Output: core.#Platform & {
|
||||
metadata: name: Name
|
||||
|
||||
spec: {
|
||||
@@ -164,3 +164,34 @@ _Projects: #Projects
|
||||
// #Selector represents label selectors.
|
||||
#Selector: [string]: matchLabels: {[string]: string}
|
||||
_Selector: #Selector
|
||||
|
||||
// #AppInfo represents the data structure for an application deployed onto the
|
||||
// platform.
|
||||
#AppInfo: {
|
||||
metadata: {
|
||||
name: string
|
||||
namespace: string
|
||||
labels: {[string]: string}
|
||||
annotations: {[string]: string}
|
||||
}
|
||||
|
||||
spec: env: string
|
||||
spec: component: string
|
||||
|
||||
spec: region: hostname: string
|
||||
spec: global: hostname: string
|
||||
|
||||
spec: dns: segments: {
|
||||
env: [] | [string]
|
||||
name: [] | [string]
|
||||
cluster: [] | [string]
|
||||
domain: [] | [string]
|
||||
}
|
||||
|
||||
// The primary port for HTTPRoute
|
||||
spec: port: number
|
||||
|
||||
spec: selector: matchLabels: {[string]: string}
|
||||
|
||||
status: component: string
|
||||
}
|
||||
|
||||
52
internal/render/component.go
Normal file
52
internal/render/component.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/api/core/v1alpha2"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
"github.com/holos-run/holos/internal/util"
|
||||
)
|
||||
|
||||
const KubernetesObjectsKind = "KubernetesObjects"
|
||||
|
||||
// KubernetesObjects represents CUE output which directly provides Kubernetes api objects to holos.
|
||||
type KubernetesObjects struct {
|
||||
Component v1alpha2.KubernetesObjects `json:"component" yaml:"component"`
|
||||
}
|
||||
|
||||
// Render produces kubernetes api objects from the APIObjectMap of the holos component.
|
||||
func (o *KubernetesObjects) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
|
||||
result := NewResult(o.Component.HolosComponent)
|
||||
result.addObjectMap(ctx, o.Component.APIObjectMap)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// KustomizeBuild renders plain yaml files in the holos component directory
|
||||
// using kubectl kustomize build.
|
||||
type KustomizeBuild struct {
|
||||
Component v1alpha2.KustomizeBuild `json:"component" yaml:"component"`
|
||||
}
|
||||
|
||||
// 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.InstancePath) (*Result, error) {
|
||||
if kb == nil {
|
||||
return nil, nil
|
||||
}
|
||||
log := logger.FromContext(ctx)
|
||||
result := NewResult(kb.Component.HolosComponent)
|
||||
// Run kustomize.
|
||||
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", string(path))
|
||||
if err != nil {
|
||||
log.ErrorContext(ctx, kOut.Stderr.String())
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
// Replace the accumulated output
|
||||
result.accumulatedOutput = kOut.Stdout.String()
|
||||
// Add CUE based api objects.
|
||||
result.addObjectMap(ctx, kb.Component.APIObjectMap)
|
||||
return result, nil
|
||||
}
|
||||
171
internal/render/helm.go
Normal file
171
internal/render/helm.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/api/core/v1alpha2"
|
||||
core "github.com/holos-run/holos/api/core/v1alpha2"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
"github.com/holos-run/holos/internal/util"
|
||||
)
|
||||
|
||||
type HelmChart struct {
|
||||
Component core.HelmChart `json:"component"`
|
||||
}
|
||||
|
||||
func (hc *HelmChart) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
|
||||
if hc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
result := NewResult(hc.Component.HolosComponent)
|
||||
if err := hc.helm(ctx, result, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.addObjectMap(ctx, hc.Component.APIObjectMap)
|
||||
if err := result.kustomize(ctx); err != nil {
|
||||
return nil, errors.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.InstancePath) error {
|
||||
log := logger.FromContext(ctx).With("chart", hc.Component.Chart.Name)
|
||||
if hc.Component.Chart.Name == "" {
|
||||
log.WarnContext(ctx, "skipping helm: no chart name specified, use a different component type")
|
||||
return nil
|
||||
}
|
||||
|
||||
cachedChartPath := filepath.Join(string(path), v1alpha2.ChartDir, filepath.Base(hc.Component.Chart.Name))
|
||||
if isNotExist(cachedChartPath) {
|
||||
// Add repositories
|
||||
repo := hc.Component.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 errors.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 errors.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, v1alpha2.ChartDir, hc.Component.Chart); err != nil {
|
||||
return fmt.Errorf("could not cache chart: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Write values file
|
||||
tempDir, err := os.MkdirTemp("", "holos")
|
||||
if err != nil {
|
||||
return errors.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.Component.ValuesContent), 0644); err != nil {
|
||||
return errors.Wrap(fmt.Errorf("could not write values: %w", err))
|
||||
}
|
||||
log.DebugContext(ctx, "helm: wrote values", "path", valuesPath, "bytes", len(hc.Component.ValuesContent))
|
||||
|
||||
// Run charts
|
||||
chart := hc.Component.Chart
|
||||
args := []string{"template"}
|
||||
if !hc.Component.EnableHooks {
|
||||
args = append(args, "--no-hooks")
|
||||
}
|
||||
namespace := hc.Component.Metadata.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 errors.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.
|
||||
//
|
||||
// It is assumed that the only method responsible for writing to chartDir is
|
||||
// cacheChart itself.
|
||||
//
|
||||
// This relies on the atomicity of moving temporary directories into place on
|
||||
// the same filesystem via os.Rename. If a syscall.EEXIST error occurs during
|
||||
// renaming, it indicates that the cached chart already exists, which is an
|
||||
// expected scenario when this function is called concurrently.
|
||||
func cacheChart(ctx context.Context, path holos.InstancePath, chartDir string, chart v1alpha2.Chart) error {
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
cacheTemp, err := os.MkdirTemp(string(path), chartDir)
|
||||
if err != nil {
|
||||
return errors.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 errors.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.MkdirAll(cachePath, 0777); err != nil {
|
||||
return errors.Wrap(fmt.Errorf("could not mkdir: %w", err))
|
||||
}
|
||||
|
||||
items, err := os.ReadDir(cacheTemp)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("could not read directory: %w", err))
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
src := filepath.Join(cacheTemp, item.Name())
|
||||
dst := filepath.Join(cachePath, item.Name())
|
||||
log.DebugContext(ctx, "rename", "src", src, "dst", dst)
|
||||
if err := os.Rename(src, dst); err != nil {
|
||||
var linkErr *os.LinkError
|
||||
if errors.As(err, &linkErr) && errors.Is(linkErr.Err, syscall.EEXIST) {
|
||||
log.DebugContext(ctx, "cache already exists", "chart", chart.Name, "chart_version", chart.Version, "path", cachePath)
|
||||
} else {
|
||||
return errors.Wrap(fmt.Errorf("could not rename: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.InfoContext(ctx, "cached", "chart", chart.Name, "chart_version", chart.Version, "path", cachePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
func isNotExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
@@ -6,27 +6,58 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/holos-run/holos/api/v1alpha1"
|
||||
core "github.com/holos-run/holos/api/core/v1alpha2"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
"github.com/holos-run/holos/internal/util"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func Platform(ctx context.Context, pf *v1alpha1.Platform, stderr io.Writer) error {
|
||||
func Platform(ctx context.Context, concurrency int, pf *core.Platform, stderr io.Writer) error {
|
||||
total := len(pf.Spec.Components)
|
||||
for idx, component := range pf.Spec.Components {
|
||||
start := time.Now()
|
||||
log := logger.FromContext(ctx).With("path", component.Path, "cluster", component.Cluster, "num", idx+1, "total", total)
|
||||
log.DebugContext(ctx, "render component")
|
||||
// Execute a sub-process to limit CUE memory usage.
|
||||
args := []string{"render", "component", "--cluster-name", component.Cluster, component.Path}
|
||||
result, err := util.RunCmd(ctx, "holos", args...)
|
||||
if err != nil {
|
||||
_, _ = io.Copy(stderr, result.Stderr)
|
||||
return errors.Wrap(fmt.Errorf("could not render component: %w", err))
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
// Limit the number of concurrent goroutines due to CUE memory usage concerns
|
||||
// while rendering components. One more for the producer.
|
||||
g.SetLimit(concurrency + 1)
|
||||
|
||||
// Spawn a producer because g.Go() blocks when the group limit is reached.
|
||||
g.Go(func() error {
|
||||
for idx, component := range pf.Spec.Components {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
// Capture idx and component to avoid issues with closure. Can be removed on Go 1.22.
|
||||
idx, component := idx, component
|
||||
// Worker go routine. Blocks if limit has been reached.
|
||||
g.Go(func() error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
start := time.Now()
|
||||
log := logger.FromContext(ctx).With("path", component.Path, "cluster", component.Cluster, "num", idx+1, "total", total)
|
||||
log.DebugContext(ctx, "render component")
|
||||
|
||||
// Execute a sub-process to limit CUE memory usage.
|
||||
args := []string{"render", "component", "--cluster-name", component.Cluster, component.Path}
|
||||
result, err := util.RunCmd(ctx, "holos", args...)
|
||||
if err != nil {
|
||||
_, _ = io.Copy(stderr, result.Stderr)
|
||||
return errors.Wrap(fmt.Errorf("could not render component: %w", err))
|
||||
}
|
||||
|
||||
duration := time.Since(start)
|
||||
log.InfoContext(ctx, "ok render component", "duration", duration)
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
duration := time.Since(start)
|
||||
log.InfoContext(ctx, "ok render component", "duration", duration)
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
|
||||
// Wait for completion and return the first error (if any)
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
230
internal/render/result.go
Normal file
230
internal/render/result.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/holos-run/holos/api/core/v1alpha2"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
"github.com/holos-run/holos/internal/util"
|
||||
)
|
||||
|
||||
// NewResult returns a new Result with the given holos component.
|
||||
func NewResult(component v1alpha2.HolosComponent) *Result {
|
||||
return &Result{
|
||||
Kind: "Result",
|
||||
APIVersion: "v1alpha2",
|
||||
Component: component,
|
||||
accumulatedOutput: "",
|
||||
}
|
||||
}
|
||||
|
||||
// Result is the build result for display or writing. Holos components Render
|
||||
// the Result as a data pipeline.
|
||||
type Result struct {
|
||||
// Kind is a string value representing the resource this object represents.
|
||||
Kind string `json:"kind" yaml:"kind" cue:"string | *\"Result\""`
|
||||
// APIVersion represents the versioned schema of this representation of an object.
|
||||
APIVersion string `json:"apiVersion" yaml:"apiVersion" cue:"string | *\"v1alpha2\""`
|
||||
|
||||
// Component represents the common fields of all holos component kinds.
|
||||
Component v1alpha2.HolosComponent
|
||||
|
||||
// accumulatedOutput accumulates rendered api objects.
|
||||
accumulatedOutput string
|
||||
}
|
||||
|
||||
func (r *Result) GetAPIVersion() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return r.APIVersion
|
||||
}
|
||||
|
||||
func (r *Result) GetKind() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return r.Kind
|
||||
}
|
||||
|
||||
// Continue returns true if the result should be skipped over.
|
||||
func (r *Result) Continue() bool {
|
||||
// Skip over a nil result
|
||||
if r == nil {
|
||||
return true
|
||||
}
|
||||
return r.Component.Skip
|
||||
}
|
||||
|
||||
// Name returns the name of the component from the Metadata field.
|
||||
func (r *Result) Name() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return r.Component.Metadata.Name
|
||||
}
|
||||
|
||||
// Filename returns the filename representing the rendered api objects of the Result.
|
||||
func (r *Result) Filename(writeTo string, cluster string) string {
|
||||
name := r.Name()
|
||||
return filepath.Join(writeTo, "clusters", cluster, "components", name, name+".gen.yaml")
|
||||
}
|
||||
|
||||
// KustomizationFilename returns the Flux Kustomization file path.
|
||||
//
|
||||
// Deprecated: Use DeployFiles instead.
|
||||
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 {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return r.accumulatedOutput
|
||||
}
|
||||
|
||||
// addObjectMap renders the provided APIObjectMap into the accumulated output.
|
||||
func (r *Result) addObjectMap(ctx context.Context, objectMap v1alpha2.APIObjectMap) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
log := logger.FromContext(ctx)
|
||||
b := []byte(r.AccumulatedOutput())
|
||||
kinds := make([]v1alpha2.Kind, 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([]v1alpha2.Label, 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 {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
log := logger.FromContext(ctx)
|
||||
if r.Component.Kustomize.ResourcesFile == "" {
|
||||
log.DebugContext(ctx, "skipping kustomize: no resourcesFile")
|
||||
return nil
|
||||
}
|
||||
if len(r.Component.Kustomize.KustomizeFiles) < 1 {
|
||||
log.DebugContext(ctx, "skipping kustomize: no kustomizeFiles")
|
||||
return nil
|
||||
}
|
||||
tempDir, err := os.MkdirTemp("", "holos.kustomize")
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
defer util.Remove(ctx, tempDir)
|
||||
|
||||
// Write the main api object resources file for kustomize.
|
||||
target := filepath.Join(tempDir, r.Component.Kustomize.ResourcesFile)
|
||||
b := []byte(r.AccumulatedOutput())
|
||||
b = util.EnsureNewline(b)
|
||||
if err := os.WriteFile(target, b, 0644); err != nil {
|
||||
return errors.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.Component.Kustomize.KustomizeFiles {
|
||||
target := filepath.Join(tempDir, string(file))
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
b := []byte(content)
|
||||
b = util.EnsureNewline(b)
|
||||
if err := os.WriteFile(target, b, 0644); err != nil {
|
||||
return errors.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 errors.Wrap(err)
|
||||
}
|
||||
// Replace the accumulated output
|
||||
r.accumulatedOutput = kOut.Stdout.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Result) WriteDeployFiles(ctx context.Context, path string) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
log := logger.FromContext(ctx)
|
||||
if len(r.Component.DeployFiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
for k, content := range r.Component.DeployFiles {
|
||||
path := filepath.Join(path, string(k))
|
||||
if err := r.Save(ctx, path, string(content)); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.InfoContext(ctx, "wrote deploy file", "path", path, "bytes", len(content))
|
||||
}
|
||||
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 errors.Wrap(err)
|
||||
}
|
||||
// Write the file content
|
||||
if err := os.WriteFile(path, []byte(content), os.FileMode(0644)); err != nil {
|
||||
log.WarnContext(ctx, "could not write", "path", path, "err", err)
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.DebugContext(ctx, "out: wrote "+path, "action", "write", "path", path, "status", "ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SkipWriteAccumulatedOutput returns true if writing the accumulated output of
|
||||
// k8s api objects should be skipped. Useful for results which only write
|
||||
// deployment files, like Flux or ArgoCD GitOps resources.
|
||||
func (r *Result) SkipWriteAccumulatedOutput() bool {
|
||||
if r == nil {
|
||||
return true
|
||||
}
|
||||
// This is a hack and should be moved to a HolosComponent field or similar.
|
||||
if strings.HasPrefix(r.Component.Metadata.Name, "gitops/") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user