Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd56f3118c | ||
|
|
d3aa748e92 | ||
|
|
6738248756 | ||
|
|
011b488775 | ||
|
|
c8d89f3291 | ||
|
|
a44ebe5171 | ||
|
|
66a3b6a874 | ||
|
|
0886788238 | ||
|
|
4fd6785a10 | ||
|
|
e4695fa204 | ||
|
|
4cd9395e6c | ||
|
|
6f4f355ee0 | ||
|
|
0fde16f477 | ||
|
|
426b4323f7 | ||
|
|
ee1e4988a6 | ||
|
|
5c391e8444 | ||
|
|
aba1b44f4d | ||
|
|
44f9615a93 | ||
|
|
69a6d2acad | ||
|
|
d2c94dc8df | ||
|
|
01720b38fc | ||
|
|
632e3c2725 | ||
|
|
18e0b48012 | ||
|
|
7dfa9dcb93 | ||
|
|
3dedd857ea | ||
|
|
cd379167cc | ||
|
|
52a5348f82 | ||
|
|
7c5c8fe692 | ||
|
|
ec371ed688 | ||
|
|
1984410577 | ||
|
|
438e01fbad | ||
|
|
d122cadae6 | ||
|
|
53fcdf307b | ||
|
|
5e6bb96147 | ||
|
|
94d03f9c59 | ||
|
|
0c01c9177d | ||
|
|
36f542da7a | ||
|
|
e41f0aa70c | ||
|
|
351c8ba74a | ||
|
|
a0e0b5bb75 | ||
|
|
7bc94e314c | ||
|
|
9681ce02e7 | ||
|
|
07e8e9f5f3 | ||
|
|
437d8a7824 | ||
|
|
6cc8214636 | ||
|
|
7d8f324014 | ||
|
|
8555d56f8c | ||
|
|
5884d720f2 | ||
|
|
92d274ced1 | ||
|
|
4dd78bd826 | ||
|
|
aeb8fd8e72 | ||
|
|
1e9744f748 | ||
|
|
a95abe65f6 | ||
|
|
c58510b92c | ||
|
|
302a7bfcf0 | ||
|
|
decbbaab0f | ||
|
|
822f599202 | ||
|
|
6a47edbc3d | ||
|
|
67d00f1dd4 | ||
|
|
bfa02cd6ed | ||
|
|
2d8ca474f3 | ||
|
|
c7cd6f5190 | ||
|
|
21e3e6f5e4 | ||
|
|
16a4f89c2f | ||
|
|
f15dea5ee7 | ||
|
|
a3bbadd1f5 | ||
|
|
30cbb0d537 | ||
|
|
6041fd4d76 | ||
|
|
fec1de0004 | ||
|
|
6ad24a6eec | ||
|
|
57dedc6450 | ||
|
|
8d2a9dd659 | ||
|
|
e3c53f5655 | ||
|
|
3b833cdacd | ||
|
|
31d1086345 | ||
|
|
b737543c13 | ||
|
|
8e150ee0d7 | ||
|
|
117a2a886d | ||
|
|
79b41dfbf5 | ||
|
|
55562f9d83 | ||
|
|
e0a636f183 | ||
|
|
1fa74214cf | ||
|
|
e5851cac57 | ||
|
|
4a26662b92 | ||
|
|
6bc6888ffc | ||
|
|
dab1f305e1 | ||
|
|
fbe79dd0af | ||
|
|
6d6829b149 | ||
|
|
971a3fa280 | ||
|
|
7632344cd1 | ||
|
|
42067748ad | ||
|
|
340c3484e5 | ||
|
|
250238c286 | ||
|
|
a223e2b426 | ||
|
|
63a7da02e7 | ||
|
|
569f827e30 | ||
|
|
4a656db2ec | ||
|
|
77b0933961 | ||
|
|
3b796cfbbd | ||
|
|
8a7a010b94 | ||
|
|
2454f6e9ee | ||
|
|
63d00bfddf | ||
|
|
f34da6c24e | ||
|
|
1d98069b73 | ||
|
|
e956b64d04 | ||
|
|
054d33b498 | ||
|
|
f2f75a4e00 | ||
|
|
a0cf73faf9 | ||
|
|
d74655c632 | ||
|
|
b8019429b8 | ||
|
|
9c08214118 | ||
|
|
f58d791e03 | ||
|
|
836033e16a | ||
|
|
77279d9baf | ||
|
|
bf19aee1a7 | ||
|
|
4de88b3155 |
50
.cspell.json
@@ -5,22 +5,35 @@
|
||||
"mdx"
|
||||
],
|
||||
"words": [
|
||||
"acraccesstokens",
|
||||
"admissionregistration",
|
||||
"anthos",
|
||||
"apiextensions",
|
||||
"applicationset",
|
||||
"applicationsets",
|
||||
"appproject",
|
||||
"appprojects",
|
||||
"argoproj",
|
||||
"authcode",
|
||||
"authorizationpolicies",
|
||||
"authpolicy",
|
||||
"authproxy",
|
||||
"authroutes",
|
||||
"balancereader",
|
||||
"buildplan",
|
||||
"cainjector",
|
||||
"CAROOT",
|
||||
"certificaterequests",
|
||||
"certificatesigningrequests",
|
||||
"clsx",
|
||||
"clusterexternalsecrets",
|
||||
"clusterissuer",
|
||||
"clusterissuers",
|
||||
"clusterrole",
|
||||
"clusterrolebinding",
|
||||
"clustersecretstores",
|
||||
"CNCF",
|
||||
"CODEOWNERS",
|
||||
"configmap",
|
||||
"cookiesecret",
|
||||
"coredns",
|
||||
@@ -37,15 +50,22 @@
|
||||
"devicecode",
|
||||
"dnsmasq",
|
||||
"dscacheutil",
|
||||
"ecrauthorizationtokens",
|
||||
"entgo",
|
||||
"envoyfilters",
|
||||
"errgroup",
|
||||
"etcdsnapshotfiles",
|
||||
"externalsecret",
|
||||
"externalsecrets",
|
||||
"fctr",
|
||||
"fieldmaskpb",
|
||||
"flushcache",
|
||||
"gatewayclasses",
|
||||
"gcraccesstokens",
|
||||
"gendoc",
|
||||
"ggnpl",
|
||||
"ghaction",
|
||||
"githubaccesstokens",
|
||||
"gitops",
|
||||
"godoc",
|
||||
"golangci",
|
||||
@@ -53,16 +73,25 @@
|
||||
"grpcreflect",
|
||||
"grpcroutes",
|
||||
"grpcurl",
|
||||
"healthz",
|
||||
"helmchartconfigs",
|
||||
"helmcharts",
|
||||
"Hiera",
|
||||
"holos",
|
||||
"holoslogger",
|
||||
"horizontalpodautoscaler",
|
||||
"Hostnames",
|
||||
"httpbin",
|
||||
"httproute",
|
||||
"httproutes",
|
||||
"Infima",
|
||||
"isatty",
|
||||
"istiod",
|
||||
"jbrx",
|
||||
"jeffmccune",
|
||||
"jetstack",
|
||||
"Jsonnet",
|
||||
"kfbh",
|
||||
"killall",
|
||||
"kubeadm",
|
||||
"kubeconfig",
|
||||
@@ -71,26 +100,33 @@
|
||||
"Kustomizations",
|
||||
"kustomize",
|
||||
"ldflags",
|
||||
"leaderelection",
|
||||
"ledgerwriter",
|
||||
"libnss",
|
||||
"loadbalancer",
|
||||
"mattn",
|
||||
"mccutchen",
|
||||
"mindmap",
|
||||
"mktemp",
|
||||
"msqbn",
|
||||
"mtls",
|
||||
"Multicluster",
|
||||
"mutatingwebhookconfiguration",
|
||||
"mxcl",
|
||||
"myhostname",
|
||||
"nameserver",
|
||||
"nolint",
|
||||
"organizationconnect",
|
||||
"orgid",
|
||||
"otelconnect",
|
||||
"Parentspanid",
|
||||
"pcjc",
|
||||
"peerauthentications",
|
||||
"pflag",
|
||||
"pipefail",
|
||||
"PKCE",
|
||||
"platformconnect",
|
||||
"podcli",
|
||||
"poddisruptionbudget",
|
||||
"podinfo",
|
||||
"portmapping",
|
||||
@@ -99,24 +135,34 @@
|
||||
"protojson",
|
||||
"proxyconfigs",
|
||||
"Pulumi",
|
||||
"pushsecrets",
|
||||
"putenv",
|
||||
"qjbp",
|
||||
"quickstart",
|
||||
"readyz",
|
||||
"referencegrant",
|
||||
"referencegrants",
|
||||
"requestauthentications",
|
||||
"retryable",
|
||||
"rolebinding",
|
||||
"ropc",
|
||||
"seccomp",
|
||||
"SECRETKEY",
|
||||
"secretstore",
|
||||
"secretstores",
|
||||
"serverlb",
|
||||
"serverside",
|
||||
"serviceaccount",
|
||||
"servicebindings",
|
||||
"serviceentries",
|
||||
"spanid",
|
||||
"spiffe",
|
||||
"startupapicheck",
|
||||
"statefulset",
|
||||
"stefanprodan",
|
||||
"struct",
|
||||
"structpb",
|
||||
"subjectaccessreviews",
|
||||
"svclb",
|
||||
"systemconnect",
|
||||
"tablewriter",
|
||||
@@ -128,14 +174,18 @@
|
||||
"Tokener",
|
||||
"Traceid",
|
||||
"traefik",
|
||||
"transactionhistory",
|
||||
"uibutton",
|
||||
"unstage",
|
||||
"untar",
|
||||
"Upsert",
|
||||
"urandom",
|
||||
"usecases",
|
||||
"userconnect",
|
||||
"userdata",
|
||||
"userservice",
|
||||
"validatingwebhookconfiguration",
|
||||
"vaultdynamicsecrets",
|
||||
"virtualservices",
|
||||
"wasmplugins",
|
||||
"workloadentries",
|
||||
|
||||
@@ -11,18 +11,29 @@ import (
|
||||
|
||||
//go:generate ../../../hack/gendoc
|
||||
|
||||
// Component represents the fields common the different kinds of component. All
|
||||
// components have a name, support mixing in resources, and produce a BuildPlan.
|
||||
type ComponentFields struct {
|
||||
// Name represents the Component name.
|
||||
Name string
|
||||
// Resources are kubernetes api objects to mix into the output.
|
||||
Resources map[string]any
|
||||
// ArgoConfig represents the ArgoCD GitOps configuration for this Component.
|
||||
ArgoConfig ArgoConfig
|
||||
// BuildPlan represents the derived BuildPlan for the Holos cli to render.
|
||||
BuildPlan core.BuildPlan
|
||||
}
|
||||
|
||||
// Helm provides a BuildPlan via the Output field which contains one HelmChart
|
||||
// from package core. Useful as a convenience wrapper to render a HelmChart
|
||||
// with optional mix-in resources and Kustomization post-processing.
|
||||
type Helm struct {
|
||||
// Name represents the Component name.
|
||||
Name string
|
||||
ComponentFields `json:",inline"`
|
||||
|
||||
// Version represents the chart version.
|
||||
Version string
|
||||
// Namespace represents the helm namespace option when rendering the chart.
|
||||
Namespace string
|
||||
// Resources are kubernetes api objects to mix into the output.
|
||||
Resources map[string]any
|
||||
|
||||
// Repo represents the chart repository
|
||||
Repo struct {
|
||||
@@ -57,27 +68,23 @@ type Helm struct {
|
||||
// KustomizeResources represents additional resources files to include in the
|
||||
// kustomize resources list.
|
||||
KustomizeResources map[string]any `cue:"{[string]: {...}}"`
|
||||
|
||||
// ArgoConfig represents the ArgoCD GitOps configuration for this Component.
|
||||
ArgoConfig ArgoConfig
|
||||
|
||||
// Output represents the derived BuildPlan for the Holos cli to render.
|
||||
Output core.BuildPlan
|
||||
}
|
||||
|
||||
// Resources represents the default schema for a Kubernetes API object resource.
|
||||
// For example, a Service, Namespace or Deployment. The top level key is the
|
||||
// kind of resource so default behavior and strict schema enforcement may be
|
||||
// enforced for the kind. The second level keys are an arbitrary internal
|
||||
// label, which serves as the default value for the resource metadata name
|
||||
// field, but may differ for situations where the same resource kind and name
|
||||
// are managed in different namespaces.
|
||||
//
|
||||
// Refer to [definitions.cue] for the CUE schema definition as an example to
|
||||
// build on when defining your own Components.
|
||||
//
|
||||
// [definitions.cue]: https://github.com/holos-run/holos/blob/main/internal/generate/platforms/cue.mod/pkg/github.com/holos-run/holos/api/schema/v1alpha3/definitions.cue#L9
|
||||
// type Resources map[string]map[string]any
|
||||
// Kustomize provides a BuildPlan via the Output field which contains one
|
||||
// KustomizeBuild from package core.
|
||||
type Kustomize struct {
|
||||
ComponentFields `json:",inline"`
|
||||
// Kustomization represents the kustomize build plan for holos to render.
|
||||
Kustomization core.KustomizeBuild
|
||||
}
|
||||
|
||||
// Kubernetes provides a BuildPlan via the Output field which contains inline
|
||||
// API Objects provided directly from CUE.
|
||||
type Kubernetes struct {
|
||||
ComponentFields `json:",inline"`
|
||||
// Objects represents the kubernetes api objects for the Component.
|
||||
Objects core.KubernetesObjects
|
||||
}
|
||||
|
||||
// ArgoConfig represents the ArgoCD GitOps configuration for a Component.
|
||||
// Useful to define once at the root of the Platform configuration and reuse
|
||||
@@ -99,6 +106,8 @@ type ArgoConfig struct {
|
||||
// Application.spec.source.targetRevision field. Defaults to the branch named
|
||||
// main.
|
||||
TargetRevision string `cue:"string | *\"main\""`
|
||||
// AppProject represents the ArgoCD Project to associate the Application with.
|
||||
AppProject string `cue:"string | *\"default\""`
|
||||
}
|
||||
|
||||
// Cluster represents a cluster managed by the Platform.
|
||||
@@ -147,29 +156,71 @@ type Platform struct {
|
||||
// Output represents the core Platform spec for the holos cli to iterate over
|
||||
// and render each listed Component, injecting the Model.
|
||||
Output core.Platform
|
||||
// Domain represents the primary domain the Platform operates in. This field
|
||||
// is intended as a sensible default for component authors to reference and
|
||||
// platform operators to define.
|
||||
Domain string `cue:"string | *\"holos.localhost\""`
|
||||
}
|
||||
|
||||
// Kustomize provides a BuildPlan via the Output field which contains one
|
||||
// KustomizeBuild from package core.
|
||||
type Kustomize struct {
|
||||
// Name represents the Component name.
|
||||
// Organization represents organizational metadata useful across the platform.
|
||||
type Organization struct {
|
||||
Name string
|
||||
DisplayName string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// OrganizationStrict represents organizational metadata useful across the
|
||||
// platform. This is an example of using CUE regular expressions to constrain
|
||||
// and validate configuration.
|
||||
type OrganizationStrict struct {
|
||||
Organization `json:",inline"`
|
||||
// Name represents the organization name as a resource name. Must be 63
|
||||
// characters or less. Must start with a letter. May contain non-repeating
|
||||
// hyphens, letters, and numbers. Must end with a letter or number.
|
||||
Name string `cue:"=~ \"^[a-z][0-9a-z-]{1,61}[0-9a-z]$\" & !~ \"--\""`
|
||||
// DisplayName represents the human readable organization name.
|
||||
DisplayName string `cue:"=~ \"^[0-9A-Za-z][0-9A-Za-z ]{2,61}[0-9A-Za-z]$\" & !~ \" \""`
|
||||
}
|
||||
|
||||
// Projects represents projects managed by the platform team for use by other
|
||||
// teams using the platform.
|
||||
type Projects map[core.NameLabel]Project
|
||||
|
||||
// Project represents logical grouping of components owned by one or more teams.
|
||||
// Useful for the platform team to manage resources for project teams to use.
|
||||
type Project struct {
|
||||
// Name represents project name.
|
||||
Name string
|
||||
|
||||
// Kustomization represents the kustomize build plan for holos to render.
|
||||
Kustomization core.KustomizeBuild
|
||||
|
||||
// Output represents the derived BuildPlan for the Holos cli to render.
|
||||
Output core.BuildPlan
|
||||
// Owner represents the team who own this project.
|
||||
Owner Owner
|
||||
// Namespaces represents the namespaces assigned to this project.
|
||||
Namespaces map[core.NameLabel]Namespace
|
||||
// Hostnames represents the host names to expose for this project.
|
||||
Hostnames map[core.NameLabel]Hostname
|
||||
}
|
||||
|
||||
// Kubernetes provides a BuildPlan via the Output field which contains inline
|
||||
// API Objects provided directly from CUE.
|
||||
type Kubernetes struct {
|
||||
// Name represents the Component name.
|
||||
// Owner represents the owner of a resource. For example, the name and email
|
||||
// address of an engineering team.
|
||||
type Owner struct {
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
|
||||
// Namespace represents a Kubernetes namespace.
|
||||
type Namespace struct {
|
||||
Name string
|
||||
// Resources represents the kubernetes api objects for the Component.
|
||||
Resources map[string]any
|
||||
|
||||
// Output represents the derived BuildPlan for the Holos cli to render.
|
||||
Output core.BuildPlan
|
||||
}
|
||||
|
||||
// Hostname represents the left most dns label of a domain name.
|
||||
type Hostname struct {
|
||||
// Name represents the subdomain to expose, e.g. "www"
|
||||
Name string
|
||||
// Namespace represents the namespace metadata.name field of backend object
|
||||
// reference.
|
||||
Namespace string
|
||||
// Service represents the Service metadata.name field of backend object
|
||||
// reference.
|
||||
Service string
|
||||
// Port represents the Service port of the backend object reference.
|
||||
Port int
|
||||
}
|
||||
@@ -14,6 +14,12 @@ import "google.golang.org/protobuf/types/known/structpb"
|
||||
// output.
|
||||
type InternalLabel string
|
||||
|
||||
// NameLabel is a unique identifier useful to convert a CUE struct to a list
|
||||
// when the values have a Name field with a default value. This type is
|
||||
// intended to indicate the common use case of converting a struct to a list
|
||||
// where the Name field of the value aligns with the struct field name.
|
||||
type NameLabel string
|
||||
|
||||
// Kind is a kubernetes api object kind. Defined as a type for clarity and type
|
||||
// checking.
|
||||
type Kind string
|
||||
|
||||
265
api/core/v1alpha4/types.go
Normal file
@@ -0,0 +1,265 @@
|
||||
// Package v1alpha4 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 clusters.
|
||||
//
|
||||
// Each holos component path, e.g. `components/namespaces` produces exactly one
|
||||
// [BuildPlan] which produces an [Artifact] collection. An [Artifact] is a
|
||||
// fully rendered manifest produced from a [Transformer] sequence, which
|
||||
// transforms a [Generator] collection.
|
||||
package v1alpha4
|
||||
|
||||
//go:generate ../../../hack/gendoc
|
||||
|
||||
// BuildPlan represents a build plan for holos to execute. Each [Platform]
|
||||
// component produces exactly one BuildPlan.
|
||||
//
|
||||
// One or more [Artifact] files are produced by a BuildPlan, representing the
|
||||
// fully rendered manifests for the Kubernetes API Server.
|
||||
type BuildPlan struct {
|
||||
// Kind represents the type of the resource.
|
||||
Kind string `json:"kind" cue:"\"BuildPlan\""`
|
||||
// APIVersion represents the versioned schema of the resource.
|
||||
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha4\""`
|
||||
// Metadata represents data about the resource such as the Name.
|
||||
Metadata Metadata `json:"metadata"`
|
||||
// Spec specifies the desired state of the resource.
|
||||
Spec BuildPlanSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// BuildPlanSpec represents the specification of the [BuildPlan].
|
||||
type BuildPlanSpec struct {
|
||||
// Component represents the component that produced the build plan.
|
||||
// Represented as a path relative to the platform root.
|
||||
Component string `json:"component"`
|
||||
// Disabled causes the holos cli to disregard the build plan.
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
// Artifacts represents the artifacts for holos to build.
|
||||
Artifacts []Artifact `json:"artifacts"`
|
||||
}
|
||||
|
||||
// Artifact represents one fully rendered manifest produced by a [Transformer]
|
||||
// sequence, which transforms a [Generator] collection. A [BuildPlan] produces
|
||||
// an [Artifact] collection.
|
||||
//
|
||||
// Each Artifact produces one manifest file artifact. Generator Output values
|
||||
// are used as Transformer Inputs. The Output field of the final [Transformer]
|
||||
// should have the same value as the Artifact field.
|
||||
//
|
||||
// When there is more than one [Generator] there must be at least one
|
||||
// [Transformer] to combine outputs into one Artifact. If there is a single
|
||||
// Generator, it may directly produce the Artifact output.
|
||||
//
|
||||
// An Artifact is processed concurrently with other artifacts in the same
|
||||
// [BuildPlan]. An Artifact should not use an output from another Artifact as
|
||||
// an input. Each [Generator] may also run concurrently. Each [Transformer] is
|
||||
// executed sequentially starting after all generators have completed.
|
||||
//
|
||||
// Output fields are write-once. It is an error for multiple Generators or
|
||||
// Transformers to produce the same Output value within the context of a
|
||||
// [BuildPlan].
|
||||
type Artifact struct {
|
||||
Artifact FilePath `json:"artifact,omitempty"`
|
||||
Generators []Generator `json:"generators,omitempty"`
|
||||
Transformers []Transformer `json:"transformers,omitempty"`
|
||||
Skip bool `json:"skip,omitempty"`
|
||||
}
|
||||
|
||||
// Generator generates an intermediate manifest for a [Artifact].
|
||||
//
|
||||
// Each Generator in a [Artifact] must have a distinct Output value for a
|
||||
// [Transformer] to reference.
|
||||
//
|
||||
// Refer to [Resources], [Helm], and [File].
|
||||
type Generator struct {
|
||||
// Kind represents the kind of generator. Must be Resources, Helm, or File.
|
||||
Kind string `json:"kind" cue:"\"Resources\" | \"Helm\" | \"File\""`
|
||||
// Output represents a file for a Transformer or Artifact to consume.
|
||||
Output FilePath `json:"output"`
|
||||
// Resources generator. Ignored unless kind is Resources. Resources are
|
||||
// stored as a two level struct. The top level key is the Kind of resource,
|
||||
// e.g. Namespace or Deployment. The second level key is an arbitrary
|
||||
// InternalLabel. The third level is a map[string]any representing the
|
||||
// Resource.
|
||||
Resources Resources `json:"resources,omitempty"`
|
||||
// Helm generator. Ignored unless kind is Helm.
|
||||
Helm Helm `json:"helm,omitempty"`
|
||||
// File generator. Ignored unless kind is File.
|
||||
File File `json:"file,omitempty"`
|
||||
}
|
||||
|
||||
// Resource represents one kubernetes api object.
|
||||
type Resource map[string]any
|
||||
|
||||
// Resources represents a kubernetes resources [Generator] from CUE.
|
||||
type Resources map[Kind]map[InternalLabel]Resource
|
||||
|
||||
// File represents a simple single file copy [Generator]. Useful with a
|
||||
// [Kustomize] [Transformer] to process plain manifest files stored in the
|
||||
// component directory. Multiple File generators may be used to transform
|
||||
// multiple resources.
|
||||
type File struct {
|
||||
// Source represents a file sub-path relative to the component path.
|
||||
Source FilePath `json:"source"`
|
||||
}
|
||||
|
||||
// Helm represents a [Chart] manifest [Generator].
|
||||
type Helm struct {
|
||||
// Chart represents a helm chart to manage.
|
||||
Chart Chart `json:"chart"`
|
||||
// Values represents values for holos to marshal into values.yaml when
|
||||
// rendering the chart.
|
||||
Values Values `json:"values"`
|
||||
// EnableHooks enables helm hooks when executing the `helm template` command.
|
||||
EnableHooks bool `json:"enableHooks,omitempty"`
|
||||
// Namespace represents the helm namespace flag
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// Values represents [Helm] Chart values generated from CUE.
|
||||
type Values map[string]any
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// Transformer transforms [Generator] manifests within a [Artifact].
|
||||
type Transformer struct {
|
||||
// Kind represents the kind of transformer. Must be Kustomize, or Join.
|
||||
Kind string `json:"kind" cue:"\"Kustomize\" | \"Join\""`
|
||||
// Inputs represents the files to transform. The Output of prior Generators
|
||||
// and Transformers.
|
||||
Inputs []FilePath `json:"inputs"`
|
||||
// Output represents a file for a subsequent Transformer or Artifact to
|
||||
// consume.
|
||||
Output FilePath `json:"output"`
|
||||
// Kustomize transformer. Ignored unless kind is Kustomize.
|
||||
Kustomize Kustomize `json:"kustomize,omitempty"`
|
||||
// Join transformer. Ignored unless kind is Join.
|
||||
Join Join `json:"join,omitempty"`
|
||||
}
|
||||
|
||||
// Join represents a [Join](https://pkg.go.dev/strings#Join) [Transformer].
|
||||
// Useful for the common case of combining the output of [Helm] and [Resources]
|
||||
// [Generator] into one [Artifact] when [Kustomize] is otherwise unnecessary.
|
||||
type Join struct {
|
||||
Separator string `json:"separator" cue:"string | *\"---\\n\""`
|
||||
}
|
||||
|
||||
// Kustomize represents a kustomization [Transformer].
|
||||
type Kustomize struct {
|
||||
// Kustomization represents the decoded kustomization.yaml file
|
||||
Kustomization Kustomization `json:"kustomization"`
|
||||
// Files holds file contents for kustomize, e.g. patch files.
|
||||
Files FileContentMap `json:"files,omitempty"`
|
||||
}
|
||||
|
||||
// Kustomization represents a kustomization.yaml file for use with the
|
||||
// [Kustomize] [Transformer]. Untyped to avoid tightly coupling holos to
|
||||
// kubectl versions which was a problem for the Flux maintainers. Type checking
|
||||
// is expected to happen in CUE against the kubectl version the user prefers.
|
||||
type Kustomization map[string]any
|
||||
|
||||
// FileContent represents file contents.
|
||||
type FileContent string
|
||||
|
||||
// FileContentMap represents a mapping of file paths to file contents.
|
||||
type FileContentMap map[FilePath]FileContent
|
||||
|
||||
// FilePath represents a file path.
|
||||
type FilePath string
|
||||
|
||||
// InternalLabel is an arbitrary unique identifier internal to holos itself.
|
||||
// The holos cli is expected to never write a InternalLabel value to rendered
|
||||
// output files, therefore use a InternalLabel when the identifier must be
|
||||
// unique and internal. Defined as a type for clarity and type checking.
|
||||
type InternalLabel string
|
||||
|
||||
// Kind is a discriminator. Defined as a type for clarity and type checking.
|
||||
type Kind string
|
||||
|
||||
// NameLabel is a unique identifier useful to convert a CUE struct to a list
|
||||
// when the values have a Name field with a default value. NameLabel indicates
|
||||
// the common use case of converting a struct to a list where the Name field of
|
||||
// the value aligns with the outer struct field name.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// Outer: [NAME=_]: Name: NAME
|
||||
type NameLabel string
|
||||
|
||||
// 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.
|
||||
Kind string `json:"kind" cue:"\"Platform\""`
|
||||
// APIVersion represents the versioned schema of this resource.
|
||||
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha4\""`
|
||||
// Metadata represents data about the resource such as the Name.
|
||||
Metadata Metadata `json:"metadata"`
|
||||
|
||||
// Spec represents the specification.
|
||||
Spec PlatformSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// Metadata represents data about the resource such as the Name.
|
||||
type Metadata struct {
|
||||
// Name represents the resource name.
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// PlatformSpec represents the specification of a [Platform]. Think of a
|
||||
// platform spec as a [Component] collection for multiple kubernetes clusters
|
||||
// combined with the user-specified Platform Model.
|
||||
type PlatformSpec struct {
|
||||
// Components represents a list of holos components to manage.
|
||||
Components []Component `json:"components"`
|
||||
}
|
||||
|
||||
// Component represents the complete context necessary to produce a [BuildPlan]
|
||||
// from a [Platform] component.
|
||||
//
|
||||
// All of these fields are passed to the holos render component command using
|
||||
// flags, which in turn are injected to CUE using tags. Field names should be
|
||||
// used consistently through the platform rendering process for readability.
|
||||
type Component struct {
|
||||
// Name represents the name of the component, injected as a tag to set the
|
||||
// BuildPlan metadata.name field. Necessary for clear user feedback during
|
||||
// platform rendering.
|
||||
Name string `json:"name"`
|
||||
// Component represents the path of the component relative to the platform root.
|
||||
Component string `json:"component"`
|
||||
// Cluster is the cluster name to provide when rendering the component.
|
||||
Cluster string `json:"cluster"`
|
||||
// Environment for example, dev, test, stage, prod
|
||||
Environment string `json:"environment,omitempty"`
|
||||
// Model represents the platform model holos gets from from the
|
||||
// PlatformService.GetPlatform rpc method and provides to CUE using a tag.
|
||||
Model map[string]any `json:"model"`
|
||||
// Tags represents cue tags to inject when rendering the component. The json
|
||||
// struct tag names of other fields in this struct are reserved tag names not
|
||||
// to be used in the tags collection.
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Schema API
|
||||
# Author API
|
||||
|
||||
<DocCardList />
|
||||
@@ -3,7 +3,7 @@
|
||||
# v1alpha3
|
||||
|
||||
```go
|
||||
import "github.com/holos-run/holos/api/schema/v1alpha3"
|
||||
import "github.com/holos-run/holos/api/author/v1alpha3"
|
||||
```
|
||||
|
||||
Package v1alpha3 contains CUE definitions intended as convenience wrappers around the core data types defined in package core. The purpose of these wrappers is to make life easier for platform engineers by reducing boiler plate code and generating component build plans in a consistent manner.
|
||||
@@ -12,11 +12,19 @@ Package v1alpha3 contains CUE definitions intended as convenience wrappers aroun
|
||||
|
||||
- [type ArgoConfig](<#ArgoConfig>)
|
||||
- [type Cluster](<#Cluster>)
|
||||
- [type ComponentFields](<#ComponentFields>)
|
||||
- [type Fleet](<#Fleet>)
|
||||
- [type Helm](<#Helm>)
|
||||
- [type Hostname](<#Hostname>)
|
||||
- [type Kubernetes](<#Kubernetes>)
|
||||
- [type Kustomize](<#Kustomize>)
|
||||
- [type Namespace](<#Namespace>)
|
||||
- [type Organization](<#Organization>)
|
||||
- [type OrganizationStrict](<#OrganizationStrict>)
|
||||
- [type Owner](<#Owner>)
|
||||
- [type Platform](<#Platform>)
|
||||
- [type Project](<#Project>)
|
||||
- [type Projects](<#Projects>)
|
||||
- [type StandardFleets](<#StandardFleets>)
|
||||
|
||||
|
||||
@@ -43,6 +51,8 @@ type ArgoConfig struct {
|
||||
// Application.spec.source.targetRevision field. Defaults to the branch named
|
||||
// main.
|
||||
TargetRevision string `cue:"string | *\"main\""`
|
||||
// AppProject represents the ArgoCD Project to associate the Application with.
|
||||
AppProject string `cue:"string | *\"default\""`
|
||||
}
|
||||
```
|
||||
|
||||
@@ -62,6 +72,24 @@ type Cluster struct {
|
||||
}
|
||||
```
|
||||
|
||||
<a name="ComponentFields"></a>
|
||||
## type ComponentFields {#ComponentFields}
|
||||
|
||||
Component represents the fields common the different kinds of component. All components have a name, support mixing in resources, and produce a BuildPlan.
|
||||
|
||||
```go
|
||||
type ComponentFields struct {
|
||||
// Name represents the Component name.
|
||||
Name string
|
||||
// Resources are kubernetes api objects to mix into the output.
|
||||
Resources map[string]any
|
||||
// ArgoConfig represents the ArgoCD GitOps configuration for this Component.
|
||||
ArgoConfig ArgoConfig
|
||||
// BuildPlan represents the derived BuildPlan for the Holos cli to render.
|
||||
BuildPlan core.BuildPlan
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Fleet"></a>
|
||||
## type Fleet {#Fleet}
|
||||
|
||||
@@ -82,14 +110,12 @@ Helm provides a BuildPlan via the Output field which contains one HelmChart from
|
||||
|
||||
```go
|
||||
type Helm struct {
|
||||
// Name represents the Component name.
|
||||
Name string
|
||||
ComponentFields `json:",inline"`
|
||||
|
||||
// Version represents the chart version.
|
||||
Version string
|
||||
// Namespace represents the helm namespace option when rendering the chart.
|
||||
Namespace string
|
||||
// Resources are kubernetes api objects to mix into the output.
|
||||
Resources map[string]any
|
||||
|
||||
// Repo represents the chart repository
|
||||
Repo struct {
|
||||
@@ -124,12 +150,26 @@ type Helm struct {
|
||||
// KustomizeResources represents additional resources files to include in the
|
||||
// kustomize resources list.
|
||||
KustomizeResources map[string]any `cue:"{[string]: {...}}"`
|
||||
}
|
||||
```
|
||||
|
||||
// ArgoConfig represents the ArgoCD GitOps configuration for this Component.
|
||||
ArgoConfig ArgoConfig
|
||||
<a name="Hostname"></a>
|
||||
## type Hostname {#Hostname}
|
||||
|
||||
// Output represents the derived BuildPlan for the Holos cli to render.
|
||||
Output core.BuildPlan
|
||||
Hostname represents the left most dns label of a domain name.
|
||||
|
||||
```go
|
||||
type Hostname struct {
|
||||
// Name represents the subdomain to expose, e.g. "www"
|
||||
Name string
|
||||
// Namespace represents the namespace metadata.name field of backend object
|
||||
// reference.
|
||||
Namespace string
|
||||
// Service represents the Service metadata.name field of backend object
|
||||
// reference.
|
||||
Service string
|
||||
// Port represents the Service port of the backend object reference.
|
||||
Port int
|
||||
}
|
||||
```
|
||||
|
||||
@@ -140,13 +180,9 @@ Kubernetes provides a BuildPlan via the Output field which contains inline API O
|
||||
|
||||
```go
|
||||
type Kubernetes struct {
|
||||
// Name represents the Component name.
|
||||
Name string
|
||||
// Resources represents the kubernetes api objects for the Component.
|
||||
Resources map[string]any
|
||||
|
||||
// Output represents the derived BuildPlan for the Holos cli to render.
|
||||
Output core.BuildPlan
|
||||
ComponentFields `json:",inline"`
|
||||
// Objects represents the kubernetes api objects for the Component.
|
||||
Objects core.KubernetesObjects
|
||||
}
|
||||
```
|
||||
|
||||
@@ -157,14 +193,62 @@ Kustomize provides a BuildPlan via the Output field which contains one Kustomize
|
||||
|
||||
```go
|
||||
type Kustomize struct {
|
||||
// Name represents the Component name.
|
||||
Name string
|
||||
|
||||
ComponentFields `json:",inline"`
|
||||
// Kustomization represents the kustomize build plan for holos to render.
|
||||
Kustomization core.KustomizeBuild
|
||||
}
|
||||
```
|
||||
|
||||
// Output represents the derived BuildPlan for the Holos cli to render.
|
||||
Output core.BuildPlan
|
||||
<a name="Namespace"></a>
|
||||
## type Namespace {#Namespace}
|
||||
|
||||
Namespace represents a Kubernetes namespace.
|
||||
|
||||
```go
|
||||
type Namespace struct {
|
||||
Name string
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Organization"></a>
|
||||
## type Organization {#Organization}
|
||||
|
||||
Organization represents organizational metadata useful across the platform.
|
||||
|
||||
```go
|
||||
type Organization struct {
|
||||
Name string
|
||||
DisplayName string
|
||||
Domain string
|
||||
}
|
||||
```
|
||||
|
||||
<a name="OrganizationStrict"></a>
|
||||
## type OrganizationStrict {#OrganizationStrict}
|
||||
|
||||
OrganizationStrict represents organizational metadata useful across the platform. This is an example of using CUE regular expressions to constrain and validate configuration.
|
||||
|
||||
```go
|
||||
type OrganizationStrict struct {
|
||||
Organization `json:",inline"`
|
||||
// Name represents the organization name as a resource name. Must be 63
|
||||
// characters or less. Must start with a letter. May contain non-repeating
|
||||
// hyphens, letters, and numbers. Must end with a letter or number.
|
||||
Name string `cue:"=~ \"^[a-z][0-9a-z-]{1,61}[0-9a-z]$\" & !~ \"--\""`
|
||||
// DisplayName represents the human readable organization name.
|
||||
DisplayName string `cue:"=~ \"^[0-9A-Za-z][0-9A-Za-z ]{2,61}[0-9A-Za-z]$\" & !~ \" \""`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Owner"></a>
|
||||
## type Owner {#Owner}
|
||||
|
||||
Owner represents the owner of a resource. For example, the name and email address of an engineering team.
|
||||
|
||||
```go
|
||||
type Owner struct {
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
```
|
||||
|
||||
@@ -185,9 +269,40 @@ type Platform struct {
|
||||
// Output represents the core Platform spec for the holos cli to iterate over
|
||||
// and render each listed Component, injecting the Model.
|
||||
Output core.Platform
|
||||
// Domain represents the primary domain the Platform operates in. This field
|
||||
// is intended as a sensible default for component authors to reference and
|
||||
// platform operators to define.
|
||||
Domain string `cue:"string | *\"holos.localhost\""`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Project"></a>
|
||||
## type Project {#Project}
|
||||
|
||||
Project represents logical grouping of components owned by one or more teams. Useful for the platform team to manage resources for project teams to use.
|
||||
|
||||
```go
|
||||
type Project struct {
|
||||
// Name represents project name.
|
||||
Name string
|
||||
// Owner represents the team who own this project.
|
||||
Owner Owner
|
||||
// Namespaces represents the namespaces assigned to this project.
|
||||
Namespaces map[core.NameLabel]Namespace
|
||||
// Hostnames represents the host names to expose for this project.
|
||||
Hostnames map[core.NameLabel]Hostname
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Projects"></a>
|
||||
## type Projects {#Projects}
|
||||
|
||||
Projects represents projects managed by the platform team for use by other teams using the platform.
|
||||
|
||||
```go
|
||||
type Projects map[core.NameLabel]Project
|
||||
```
|
||||
|
||||
<a name="StandardFleets"></a>
|
||||
## type StandardFleets {#StandardFleets}
|
||||
|
||||
@@ -41,6 +41,7 @@ Note that Holos operates as a data pipeline, so the output of a [HelmChart](<#He
|
||||
- [type Kustomize](<#Kustomize>)
|
||||
- [type KustomizeBuild](<#KustomizeBuild>)
|
||||
- [type Metadata](<#Metadata>)
|
||||
- [type NameLabel](<#NameLabel>)
|
||||
- [type Platform](<#Platform>)
|
||||
- [type PlatformMetadata](<#PlatformMetadata>)
|
||||
- [type PlatformSpec](<#PlatformSpec>)
|
||||
@@ -328,6 +329,15 @@ type Metadata struct {
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NameLabel"></a>
|
||||
## type NameLabel {#NameLabel}
|
||||
|
||||
NameLabel is a unique identifier useful to convert a CUE struct to a list when the values have a Name field with a default value. This type is intended to indicate the common use case of converting a struct to a list where the Name field of the value aligns with the struct field name.
|
||||
|
||||
```go
|
||||
type NameLabel string
|
||||
```
|
||||
|
||||
<a name="Platform"></a>
|
||||
## type Platform {#Platform}
|
||||
|
||||
|
||||
407
doc/md/api/core/v1alpha4.md
Normal file
@@ -0,0 +1,407 @@
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# v1alpha4
|
||||
|
||||
```go
|
||||
import "github.com/holos-run/holos/api/core/v1alpha4"
|
||||
```
|
||||
|
||||
Package v1alpha4 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](<#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 clusters.
|
||||
|
||||
Each holos component path, e.g. \`components/namespaces\` produces exactly one [BuildPlan](<#BuildPlan>) which produces an [Artifact](<#Artifact>) collection. An [Artifact](<#Artifact>) is a fully rendered manifest produced from a [Transformer](<#Transformer>) sequence, which transforms a [Generator](<#Generator>) collection.
|
||||
|
||||
## Index
|
||||
|
||||
- [type Artifact](<#Artifact>)
|
||||
- [type BuildPlan](<#BuildPlan>)
|
||||
- [type BuildPlanSpec](<#BuildPlanSpec>)
|
||||
- [type Chart](<#Chart>)
|
||||
- [type Component](<#Component>)
|
||||
- [type File](<#File>)
|
||||
- [type FileContent](<#FileContent>)
|
||||
- [type FileContentMap](<#FileContentMap>)
|
||||
- [type FilePath](<#FilePath>)
|
||||
- [type Generator](<#Generator>)
|
||||
- [type Helm](<#Helm>)
|
||||
- [type InternalLabel](<#InternalLabel>)
|
||||
- [type Join](<#Join>)
|
||||
- [type Kind](<#Kind>)
|
||||
- [type Kustomization](<#Kustomization>)
|
||||
- [type Kustomize](<#Kustomize>)
|
||||
- [type Metadata](<#Metadata>)
|
||||
- [type NameLabel](<#NameLabel>)
|
||||
- [type Platform](<#Platform>)
|
||||
- [type PlatformSpec](<#PlatformSpec>)
|
||||
- [type Repository](<#Repository>)
|
||||
- [type Resource](<#Resource>)
|
||||
- [type Resources](<#Resources>)
|
||||
- [type Transformer](<#Transformer>)
|
||||
- [type Values](<#Values>)
|
||||
|
||||
|
||||
<a name="Artifact"></a>
|
||||
## type Artifact {#Artifact}
|
||||
|
||||
Artifact represents one fully rendered manifest produced by a [Transformer](<#Transformer>) sequence, which transforms a [Generator](<#Generator>) collection. A [BuildPlan](<#BuildPlan>) produces an [Artifact](<#Artifact>) collection.
|
||||
|
||||
Each Artifact produces one manifest file artifact. Generator Output values are used as Transformer Inputs. The Output field of the final [Transformer](<#Transformer>) should have the same value as the Artifact field.
|
||||
|
||||
When there is more than one [Generator](<#Generator>) there must be at least one [Transformer](<#Transformer>) to combine outputs into one Artifact. If there is a single Generator, it may directly produce the Artifact output.
|
||||
|
||||
An Artifact is processed concurrently with other artifacts in the same [BuildPlan](<#BuildPlan>). An Artifact should not use an output from another Artifact as an input. Each [Generator](<#Generator>) may also run concurrently. Each [Transformer](<#Transformer>) is executed sequentially starting after all generators have completed.
|
||||
|
||||
Output fields are write\-once. It is an error for multiple Generators or Transformers to produce the same Output value within the context of a [BuildPlan](<#BuildPlan>).
|
||||
|
||||
```go
|
||||
type Artifact struct {
|
||||
Artifact FilePath `json:"artifact,omitempty"`
|
||||
Generators []Generator `json:"generators,omitempty"`
|
||||
Transformers []Transformer `json:"transformers,omitempty"`
|
||||
Skip bool `json:"skip,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="BuildPlan"></a>
|
||||
## type BuildPlan {#BuildPlan}
|
||||
|
||||
BuildPlan represents a build plan for holos to execute. Each [Platform](<#Platform>) component produces exactly one BuildPlan.
|
||||
|
||||
One or more [Artifact](<#Artifact>) files are produced by a BuildPlan, representing the fully rendered manifests for the Kubernetes API Server.
|
||||
|
||||
```go
|
||||
type BuildPlan struct {
|
||||
// Kind represents the type of the resource.
|
||||
Kind string `json:"kind" cue:"\"BuildPlan\""`
|
||||
// APIVersion represents the versioned schema of the resource.
|
||||
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha4\""`
|
||||
// Metadata represents data about the resource such as the Name.
|
||||
Metadata Metadata `json:"metadata"`
|
||||
// Spec specifies the desired state of the resource.
|
||||
Spec BuildPlanSpec `json:"spec"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="BuildPlanSpec"></a>
|
||||
## type BuildPlanSpec {#BuildPlanSpec}
|
||||
|
||||
BuildPlanSpec represents the specification of the [BuildPlan](<#BuildPlan>).
|
||||
|
||||
```go
|
||||
type BuildPlanSpec struct {
|
||||
// Component represents the component that produced the build plan.
|
||||
// Represented as a path relative to the platform root.
|
||||
Component string `json:"component"`
|
||||
// Disabled causes the holos cli to disregard the build plan.
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
// Artifacts represents the artifacts for holos to build.
|
||||
Artifacts []Artifact `json:"artifacts"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Chart"></a>
|
||||
## type Chart {#Chart}
|
||||
|
||||
Chart represents a [Helm](<#Helm>) Chart.
|
||||
|
||||
```go
|
||||
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"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Component"></a>
|
||||
## type Component {#Component}
|
||||
|
||||
Component represents the complete context necessary to produce a [BuildPlan](<#BuildPlan>) from a [Platform](<#Platform>) component.
|
||||
|
||||
All of these fields are passed to the holos render component command using flags, which in turn are injected to CUE using tags. Field names should be used consistently through the platform rendering process for readability.
|
||||
|
||||
```go
|
||||
type Component struct {
|
||||
// Name represents the name of the component, injected as a tag to set the
|
||||
// BuildPlan metadata.name field. Necessary for clear user feedback during
|
||||
// platform rendering.
|
||||
Name string `json:"name"`
|
||||
// Component represents the path of the component relative to the platform root.
|
||||
Component string `json:"component"`
|
||||
// Cluster is the cluster name to provide when rendering the component.
|
||||
Cluster string `json:"cluster"`
|
||||
// Environment for example, dev, test, stage, prod
|
||||
Environment string `json:"environment,omitempty"`
|
||||
// Model represents the platform model holos gets from from the
|
||||
// PlatformService.GetPlatform rpc method and provides to CUE using a tag.
|
||||
Model map[string]any `json:"model"`
|
||||
// Tags represents cue tags to inject when rendering the component. The json
|
||||
// struct tag names of other fields in this struct are reserved tag names not
|
||||
// to be used in the tags collection.
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="File"></a>
|
||||
## type File {#File}
|
||||
|
||||
File represents a simple single file copy [Generator](<#Generator>). Useful with a [Kustomize](<#Kustomize>) [Transformer](<#Transformer>) to process plain manifest files stored in the component directory. Multiple File generators may be used to transform multiple resources.
|
||||
|
||||
```go
|
||||
type File struct {
|
||||
// Source represents a file sub-path relative to the component path.
|
||||
Source FilePath `json:"source"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="FileContent"></a>
|
||||
## type FileContent {#FileContent}
|
||||
|
||||
FileContent represents file contents.
|
||||
|
||||
```go
|
||||
type FileContent string
|
||||
```
|
||||
|
||||
<a name="FileContentMap"></a>
|
||||
## type FileContentMap {#FileContentMap}
|
||||
|
||||
FileContentMap represents a mapping of file paths to file contents.
|
||||
|
||||
```go
|
||||
type FileContentMap map[FilePath]FileContent
|
||||
```
|
||||
|
||||
<a name="FilePath"></a>
|
||||
## type FilePath {#FilePath}
|
||||
|
||||
FilePath represents a file path.
|
||||
|
||||
```go
|
||||
type FilePath string
|
||||
```
|
||||
|
||||
<a name="Generator"></a>
|
||||
## type Generator {#Generator}
|
||||
|
||||
Generator generates an intermediate manifest for a [Artifact](<#Artifact>).
|
||||
|
||||
Each Generator in a [Artifact](<#Artifact>) must have a distinct Output value for a [Transformer](<#Transformer>) to reference.
|
||||
|
||||
Refer to [Resources](<#Resources>), [Helm](<#Helm>), and [File](<#File>).
|
||||
|
||||
```go
|
||||
type Generator struct {
|
||||
// Kind represents the kind of generator. Must be Resources, Helm, or File.
|
||||
Kind string `json:"kind" cue:"\"Resources\" | \"Helm\" | \"File\""`
|
||||
// Output represents a file for a Transformer or Artifact to consume.
|
||||
Output FilePath `json:"output"`
|
||||
// Resources generator. Ignored unless kind is Resources. Resources are
|
||||
// stored as a two level struct. The top level key is the Kind of resource,
|
||||
// e.g. Namespace or Deployment. The second level key is an arbitrary
|
||||
// InternalLabel. The third level is a map[string]any representing the
|
||||
// Resource.
|
||||
Resources Resources `json:"resources,omitempty"`
|
||||
// Helm generator. Ignored unless kind is Helm.
|
||||
Helm Helm `json:"helm,omitempty"`
|
||||
// File generator. Ignored unless kind is File.
|
||||
File File `json:"file,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Helm"></a>
|
||||
## type Helm {#Helm}
|
||||
|
||||
Helm represents a [Chart](<#Chart>) manifest [Generator](<#Generator>).
|
||||
|
||||
```go
|
||||
type Helm struct {
|
||||
// Chart represents a helm chart to manage.
|
||||
Chart Chart `json:"chart"`
|
||||
// Values represents values for holos to marshal into values.yaml when
|
||||
// rendering the chart.
|
||||
Values Values `json:"values"`
|
||||
// EnableHooks enables helm hooks when executing the `helm template` command.
|
||||
EnableHooks bool `json:"enableHooks,omitempty"`
|
||||
// Namespace represents the helm namespace flag
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="InternalLabel"></a>
|
||||
## type InternalLabel {#InternalLabel}
|
||||
|
||||
InternalLabel is an arbitrary unique identifier internal to holos itself. The holos cli is expected to never write a InternalLabel value to rendered output files, therefore use a InternalLabel when the identifier must be unique and internal. Defined as a type for clarity and type checking.
|
||||
|
||||
```go
|
||||
type InternalLabel string
|
||||
```
|
||||
|
||||
<a name="Join"></a>
|
||||
## type Join {#Join}
|
||||
|
||||
Join represents a [Join](<#Join>)\(https://pkg.go.dev/strings#Join\) [Transformer](<#Transformer>). Useful for the common case of combining the output of [Helm](<#Helm>) and [Resources](<#Resources>) [Generator](<#Generator>) into one [Artifact](<#Artifact>) when [Kustomize](<#Kustomize>) is otherwise unnecessary.
|
||||
|
||||
```go
|
||||
type Join struct {
|
||||
Separator string `json:"separator" cue:"string | *\"---\\n\""`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Kind"></a>
|
||||
## type Kind {#Kind}
|
||||
|
||||
Kind is a discriminator. Defined as a type for clarity and type checking.
|
||||
|
||||
```go
|
||||
type Kind string
|
||||
```
|
||||
|
||||
<a name="Kustomization"></a>
|
||||
## type Kustomization {#Kustomization}
|
||||
|
||||
Kustomization represents a kustomization.yaml file for use with the [Kustomize](<#Kustomize>) [Transformer](<#Transformer>). Untyped to avoid tightly coupling holos to kubectl versions which was a problem for the Flux maintainers. Type checking is expected to happen in CUE against the kubectl version the user prefers.
|
||||
|
||||
```go
|
||||
type Kustomization map[string]any
|
||||
```
|
||||
|
||||
<a name="Kustomize"></a>
|
||||
## type Kustomize {#Kustomize}
|
||||
|
||||
Kustomize represents a kustomization [Transformer](<#Transformer>).
|
||||
|
||||
```go
|
||||
type Kustomize struct {
|
||||
// Kustomization represents the decoded kustomization.yaml file
|
||||
Kustomization Kustomization `json:"kustomization"`
|
||||
// Files holds file contents for kustomize, e.g. patch files.
|
||||
Files FileContentMap `json:"files,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Metadata"></a>
|
||||
## type Metadata {#Metadata}
|
||||
|
||||
Metadata represents data about the resource such as the Name.
|
||||
|
||||
```go
|
||||
type Metadata struct {
|
||||
// Name represents the resource name.
|
||||
Name string `json:"name"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NameLabel"></a>
|
||||
## type NameLabel {#NameLabel}
|
||||
|
||||
NameLabel is a unique identifier useful to convert a CUE struct to a list when the values have a Name field with a default value. NameLabel indicates the common use case of converting a struct to a list where the Name field of the value aligns with the outer struct field name.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
Outer: [NAME=_]: Name: NAME
|
||||
```
|
||||
|
||||
```go
|
||||
type NameLabel string
|
||||
```
|
||||
|
||||
<a name="Platform"></a>
|
||||
## type Platform {#Platform}
|
||||
|
||||
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.
|
||||
|
||||
```go
|
||||
type Platform struct {
|
||||
// Kind is a string value representing the resource.
|
||||
Kind string `json:"kind" cue:"\"Platform\""`
|
||||
// APIVersion represents the versioned schema of this resource.
|
||||
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha4\""`
|
||||
// Metadata represents data about the resource such as the Name.
|
||||
Metadata Metadata `json:"metadata"`
|
||||
|
||||
// Spec represents the specification.
|
||||
Spec PlatformSpec `json:"spec"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="PlatformSpec"></a>
|
||||
## type PlatformSpec {#PlatformSpec}
|
||||
|
||||
PlatformSpec represents the specification of a [Platform](<#Platform>). Think of a platform spec as a [Component](<#Component>) collection for multiple kubernetes clusters combined with the user\-specified Platform Model.
|
||||
|
||||
```go
|
||||
type PlatformSpec struct {
|
||||
// Components represents a list of holos components to manage.
|
||||
Components []Component `json:"components"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Repository"></a>
|
||||
## type Repository {#Repository}
|
||||
|
||||
Repository represents a [Helm](<#Helm>) [Chart](<#Chart>) repository.
|
||||
|
||||
```go
|
||||
type Repository struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Resource"></a>
|
||||
## type Resource {#Resource}
|
||||
|
||||
Resource represents one kubernetes api object.
|
||||
|
||||
```go
|
||||
type Resource map[string]any
|
||||
```
|
||||
|
||||
<a name="Resources"></a>
|
||||
## type Resources {#Resources}
|
||||
|
||||
Resources represents a kubernetes resources [Generator](<#Generator>) from CUE.
|
||||
|
||||
```go
|
||||
type Resources map[Kind]map[InternalLabel]Resource
|
||||
```
|
||||
|
||||
<a name="Transformer"></a>
|
||||
## type Transformer {#Transformer}
|
||||
|
||||
Transformer transforms [Generator](<#Generator>) manifests within a [Artifact](<#Artifact>).
|
||||
|
||||
```go
|
||||
type Transformer struct {
|
||||
// Kind represents the kind of transformer. Must be Kustomize, or Join.
|
||||
Kind string `json:"kind" cue:"\"Kustomize\" | \"Join\""`
|
||||
// Inputs represents the files to transform. The Output of prior Generators
|
||||
// and Transformers.
|
||||
Inputs []FilePath `json:"inputs"`
|
||||
// Output represents a file for a subsequent Transformer or Artifact to
|
||||
// consume.
|
||||
Output FilePath `json:"output"`
|
||||
// Kustomize transformer. Ignored unless kind is Kustomize.
|
||||
Kustomize Kustomize `json:"kustomize,omitempty"`
|
||||
// Join transformer. Ignored unless kind is Join.
|
||||
Join Join `json:"join,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Values"></a>
|
||||
## type Values {#Values}
|
||||
|
||||
Values represents [Helm](<#Helm>) Chart values generated from CUE.
|
||||
|
||||
```go
|
||||
type Values map[string]any
|
||||
```
|
||||
|
||||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
|
||||
724
doc/md/archive/guides/2024-09-15-quickstart.mdx
Normal file
@@ -0,0 +1,724 @@
|
||||
---
|
||||
description: Try Holos with this quick start guide.
|
||||
slug: /archive/2024-09-15-quickstart
|
||||
sidebar_position: 100
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Admonition from '@theme/Admonition';
|
||||
|
||||
# Quickstart
|
||||
|
||||
In this guide, you'll experience how Holos makes the process of operating a
|
||||
Platform safer, easier, and more consistent. We'll use Holos to manage a
|
||||
vendor-provided Helm chart as a Component. Next, we'll mix in our own custom
|
||||
resources to manage the Component with GitOps. Finally, you'll see how Holos
|
||||
makes it safer and easier to maintain software over time by surfacing the exact
|
||||
changes that will be applied when upgrading the vendor's chart to a new version,
|
||||
before they are actually made.
|
||||
|
||||
The [Concepts](/docs/concepts) page defines capitalized terms such as Platform
|
||||
and Component.
|
||||
|
||||
## What you'll need {#requirements}
|
||||
|
||||
You'll need the following tools installed to complete this guide.
|
||||
|
||||
1. [holos](/docs/install) - to build the Platform.
|
||||
2. [helm](https://helm.sh/docs/intro/install/) - to render Holos Components that
|
||||
wrap upstream Helm charts.
|
||||
|
||||
Optionally, if you'd like to apply the rendered manifests to a real Cluster,
|
||||
first complete the [Local Cluster Guide](/docs/guides/local-cluster).
|
||||
|
||||
## Install Holos
|
||||
|
||||
Install Holos with the following command or other methods listed on the
|
||||
[Installation](/docs/install/) page.
|
||||
|
||||
```bash
|
||||
go install github.com/holos-run/holos/cmd/holos@latest
|
||||
```
|
||||
|
||||
## Create a Git Repository
|
||||
|
||||
Start by initializing an empty Git repository. Holos operates on local files
|
||||
stored in a Git repository.
|
||||
|
||||
<Tabs groupId="init">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
mkdir holos-quickstart
|
||||
cd holos-quickstart
|
||||
git init
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
Initialized empty Git repository in /holos-quickstart/.git/
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This guide assumes you will run commands from the root directory of the Git
|
||||
repository unless stated otherwise.
|
||||
|
||||
## Generate the Platform {#Generate-Platform}
|
||||
|
||||
Generate the Platform code in the repository root. A Platform refers to the
|
||||
entire set of software holistically integrated to provide a software development
|
||||
platform for your organization. In this guide, the Platform will include a
|
||||
single Component to demonstrate how the concepts fit together.
|
||||
|
||||
```bash
|
||||
holos generate platform quickstart
|
||||
```
|
||||
|
||||
Commit the generated platform config to the repository.
|
||||
|
||||
<Tabs groupId="commit-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "holos generate platform quickstart - $(holos --version)"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main (root-commit) 0b17b7f] holos generate platform quickstart
|
||||
213 files changed, 72349 insertions(+)
|
||||
...
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Generate a Component {#generate-component}
|
||||
|
||||
The platform you generated is currently empty. Run the following command to
|
||||
generate the CUE code that defines a Helm Component.
|
||||
|
||||
<Tabs groupId="gen-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos generate component podinfo --component-version 6.6.1
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
generated component
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The --component-version 6.6.1 flag intentionally installs an older release.
|
||||
You'll see how Holos assists with software upgrades later in this guide.
|
||||
|
||||
The generate component command creates two files: a leaf file,
|
||||
`components/podinfo/podinfo.gen.cue`, and a root file, `podinfo.gen.cue`. Holos
|
||||
leverages the fact that [order is
|
||||
irrelevant](https://cuelang.org/docs/tour/basics/order-irrelevance/) in CUE to
|
||||
register the component with the Platform by adding a file to the root of the Git
|
||||
repository. The second file defines the component in the leaf component
|
||||
directory.
|
||||
|
||||
<Tabs groupId="podinfo-files">
|
||||
<TabItem value="components/podinfo/podinfo.gen.cue" label="Leaf">
|
||||
`components/podinfo/podinfo.gen.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
|
||||
let Chart = {
|
||||
Name: "podinfo"
|
||||
Version: "6.6.1"
|
||||
Namespace: "default"
|
||||
|
||||
Repo: name: "podinfo"
|
||||
Repo: url: "https://stefanprodan.github.io/podinfo"
|
||||
|
||||
Values: {}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="podinfo.gen.cue" label="Root">
|
||||
`podinfo.gen.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Manage podinfo on workload clusters only
|
||||
for Cluster in #Fleets.workload.clusters {
|
||||
#Platform: Components: "\(Cluster.name)/podinfo": {
|
||||
path: "components/podinfo"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In this example, we provide the minimal information needed to manage the Helm
|
||||
chart: the name, version, Kubernetes namespace for deployment, and the chart
|
||||
repository location.
|
||||
|
||||
This chart deploys cleanly without any values provided, but we include an empty
|
||||
Values struct to show how Holos improves consistency and safety in Helm by
|
||||
leveraging the strong type-checking in CUE. You can safely pass shared values,
|
||||
such as the organization’s domain name, to all Components across all clusters in
|
||||
the Platform by defining them at the root of the configuration.
|
||||
|
||||
Commit the generated component config to the repository.
|
||||
|
||||
<Tabs groupId="commit-component">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "holos generate component podinfo - $(holos --version)"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main cc0e90c] holos generate component podinfo
|
||||
2 files changed, 24 insertions(+)
|
||||
create mode 100644 components/podinfo/podinfo.gen.cue
|
||||
create mode 100644 podinfo.gen.cue
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Render the Component
|
||||
|
||||
You can render individual components without adding them to a Platform, which is
|
||||
helpful when developing a new component.
|
||||
|
||||
<Tabs groupId="render-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render component ./components/podinfo --cluster-name=default
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
cached
|
||||
rendered podinfo
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
First, the command caches the Helm chart locally to speed up subsequent
|
||||
renderings. Then, the command runs Helm to produce the output and writes it into
|
||||
the deploy directory.
|
||||
|
||||
<Tabs groupId="tree-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
tree deploy
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
deploy
|
||||
└── clusters
|
||||
└── default
|
||||
└── components
|
||||
└── podinfo
|
||||
└── podinfo.gen.yaml
|
||||
|
||||
5 directories, 1 file
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The component deploys to one cluster named `default`. In practice, the same
|
||||
component is often deployed to multiple clusters, such as `east` and `west` to
|
||||
provide redundancy and increase availability.
|
||||
|
||||
:::tip
|
||||
This example is equivalent to running `helm template` on the chart and saving
|
||||
the output to a file. Holos simplifies this task, making it safer and more
|
||||
consistent when managing many charts.
|
||||
:::
|
||||
|
||||
## Mix in an ArgoCD Application
|
||||
|
||||
We've seen how Holos works with Helm, but we haven't yet explored how Holos
|
||||
makes it easier to consistently and safely manage all of the software in a
|
||||
Platform.
|
||||
|
||||
Holos allows you to easily mix in resources that differentiate your Platform.
|
||||
We'll use this feature to mix in an ArgoCD [Application][application] to manage
|
||||
the podinfo Component with GitOps. We'll define this configuration in a way that
|
||||
can be automatically and consistently reused across all future Components added
|
||||
to the Platform.
|
||||
|
||||
Create a new file named `argocd.cue` in the root of your Git repository with the
|
||||
following contents:
|
||||
|
||||
<Tabs groupId="argocd-config">
|
||||
<TabItem value="command" label="argocd.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
#ArgoConfig: {
|
||||
Enabled: true
|
||||
RepoURL: "https://github.com/holos-run/holos-quickstart-guide"
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
If you plan to apply the rendered output to a real cluster, change the
|
||||
`example.com` RepoURL to the URL of the Git repository you created in this
|
||||
guide. You don't need to change the example if you're just exploring Holos by
|
||||
inspecting the rendered output without applying it to a live cluster.
|
||||
:::
|
||||
|
||||
With this file in place, render the component again.
|
||||
|
||||
<Tabs groupId="render-podinfo-argocd">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render component ./components/podinfo --cluster-name=default
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
wrote deploy file
|
||||
rendered gitops/podinfo
|
||||
rendered podinfo
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Holos uses the locally cached chart to improve performance and reliability. It
|
||||
then renders the Helm template output along with an ArgoCD Application resource
|
||||
for GitOps.
|
||||
|
||||
:::tip
|
||||
By defining the ArgoCD configuration at the root, we again take advantage of the
|
||||
fact that [order is
|
||||
irrelevant](https://cuelang.org/docs/tour/basics/order-irrelevance/) in CUE.
|
||||
:::
|
||||
|
||||
Defining the configuration at the root ensures all future leaf Components take
|
||||
the ArgoCD configuration and render an Application manifest for GitOps
|
||||
management.
|
||||
|
||||
<Tabs groupId="tree-podinfo-argocd">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
tree deploy
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
deploy
|
||||
└── clusters
|
||||
└── default
|
||||
├── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
└── gitops
|
||||
└── podinfo.application.gen.yaml
|
||||
|
||||
6 directories, 2 files
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Notice the new `podinfo.application.gen.yaml` file created by enabling ArgoCD in
|
||||
the Helm component. The Application resource in the file looks like this:
|
||||
|
||||
<Tabs groupId="podinfo-application">
|
||||
<TabItem value="file" label="podinfo.application.gen.yaml">
|
||||
```yaml showLineNumbers
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: argocd
|
||||
spec:
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
project: default
|
||||
source:
|
||||
path: ./deploy/clusters/default/components/podinfo
|
||||
repoURL: https://example.com/holos-quickstart.git
|
||||
targetRevision: main
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
Holos generates a similar Application resource for every additional Component
|
||||
added to your Platform.
|
||||
:::
|
||||
|
||||
Finally, add and commit the results to your Platform's Git repository.
|
||||
|
||||
<Tabs groupId="commit-argo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "holos render component ./components/podinfo --cluster-name=default"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main f95cef1] holos render component ./components/podinfo --cluster-name=default
|
||||
3 files changed, 134 insertions(+)
|
||||
create mode 100644 argocd.cue
|
||||
create mode 100644 deploy/clusters/default/components/podinfo/podinfo.gen.yaml
|
||||
create mode 100644 deploy/clusters/default/gitops/podinfo.application.gen.yaml
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In this section, we learned how Holos simplifies mixing resources into
|
||||
Components, like an ArgoCD Application. Holos ensures consistency by managing an
|
||||
Application resource for every Component added to the Platform through the
|
||||
configuration you define in `argocd.cue` at the root of the repository.
|
||||
|
||||
## Define Workload Clusters {#workload-clusters}
|
||||
|
||||
We've generated a Component to manage podinfo and integrated it with our
|
||||
Platform, but rendering the Platform doesn't render podinfo. Podinfo isn't
|
||||
rendered because we haven't assigned any Clusters to the workload Fleet.
|
||||
|
||||
Define two new clusters, `east` and `west`, and assign them to the workload
|
||||
Fleet. Create a new file named `clusters.cue` in the root of your Git repository
|
||||
with the following contents:
|
||||
|
||||
<Tabs groupId="clusters">
|
||||
<TabItem value="clusters.cue" label="clusters.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Define two workload clusters for disaster recovery.
|
||||
#Fleets: workload: clusters: {
|
||||
// In CUE _ indicates values are defined elsewhere.
|
||||
east: _
|
||||
west: _
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This example shows how Holos simplifies configuring multiple clusters with
|
||||
similar configuration by grouping them into a Fleet.
|
||||
|
||||
:::tip
|
||||
Fleets help segment a group of Clusters into one leader and multiple followers
|
||||
by designating one cluster as the primary. Holos makes it safer, easier, and
|
||||
more consistent to reconfigure which cluster is the primary. The primary can be
|
||||
set to automatically restore persistent data from backups, while non-primary
|
||||
clusters can be configured to automatically replicate from the primary.
|
||||
|
||||
Automatic database backup, restore, and streaming replication is an advanced
|
||||
topic enabled by Cloud Native PG and CUE. Check back for a guide on this and
|
||||
other Day 2 operations topics.
|
||||
:::
|
||||
|
||||
## Render the Platform {#render-platform}
|
||||
|
||||
Render the Platform to render the podinfo Component for each of the workload
|
||||
clusters.
|
||||
|
||||
<Tabs groupId="render-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered components/podinfo for cluster west in 99.480792ms
|
||||
rendered components/podinfo for cluster east in 99.882667ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The render platform command iterates over every Cluster in the Fleet and renders
|
||||
each Component assigned to the Fleet. Notice the two additional subdirectories
|
||||
created under the deploy directory, one for each cluster: `east` and `west`.
|
||||
|
||||
<Tabs groupId="tree-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
tree deploy
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
deploy
|
||||
└── clusters
|
||||
├── default
|
||||
│ ├── components
|
||||
│ │ └── podinfo
|
||||
│ │ └── podinfo.gen.yaml
|
||||
│ └── gitops
|
||||
│ └── podinfo.application.gen.yaml
|
||||
# highlight-next-line
|
||||
├── east
|
||||
│ ├── components
|
||||
│ │ └── podinfo
|
||||
│ │ └── podinfo.gen.yaml
|
||||
│ └── gitops
|
||||
│ └── podinfo.application.gen.yaml
|
||||
# highlight-next-line
|
||||
└── west
|
||||
├── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
└── gitops
|
||||
└── podinfo.application.gen.yaml
|
||||
|
||||
14 directories, 6 files
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Holos ensures consistency and safety by defining the ArgoCD Application once,
|
||||
with strong type checking, at the configuration root.
|
||||
|
||||
New Application resources are automatically generated for the `east` and `west`
|
||||
workload Clusters.
|
||||
|
||||
<Tabs groupId="applications">
|
||||
<TabItem value="east" label="east">
|
||||
`deploy/clusters/east/gitops/podinfo.application.gen.yaml`
|
||||
```yaml showLineNumbers
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: argocd
|
||||
spec:
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
project: default
|
||||
source:
|
||||
# highlight-next-line
|
||||
path: ./deploy/clusters/east/components/podinfo
|
||||
repoURL: https://example.com/holos-quickstart.git
|
||||
targetRevision: main
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="west" label="west">
|
||||
`deploy/clusters/west/gitops/podinfo.application.gen.yaml`
|
||||
```yaml showLineNumbers
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: argocd
|
||||
spec:
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
project: default
|
||||
source:
|
||||
# highlight-next-line
|
||||
path: ./deploy/clusters/west/components/podinfo
|
||||
repoURL: https://example.com/holos-quickstart.git
|
||||
targetRevision: main
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="default" label="default">
|
||||
`deploy/clusters/default/gitops/podinfo.application.gen.yaml`
|
||||
```yaml showLineNumbers
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: argocd
|
||||
spec:
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
project: default
|
||||
source:
|
||||
# highlight-next-line
|
||||
path: ./deploy/clusters/default/components/podinfo
|
||||
repoURL: https://example.com/holos-quickstart.git
|
||||
targetRevision: main
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Add and commit the rendered Platform and workload Clusters.
|
||||
|
||||
<Tabs groupId="commit-render-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "holos render platform ./platform - $(holos --version)"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main 5aebcf5] holos render platform ./platform - 0.93.2
|
||||
5 files changed, 263 insertions(+)
|
||||
create mode 100644 clusters.cue
|
||||
create mode 100644 deploy/clusters/east/components/podinfo/podinfo.gen.yaml
|
||||
create mode 100644 deploy/clusters/east/gitops/podinfo.application.gen.yaml
|
||||
create mode 100644 deploy/clusters/west/components/podinfo/podinfo.gen.yaml
|
||||
create mode 100644 deploy/clusters/west/gitops/podinfo.application.gen.yaml
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Upgrade a Helm Chart
|
||||
|
||||
Holos is designed to ease the burden of Day 2 operations. With Holos, upgrading
|
||||
software, integrating new software, and making safe platform-wide configuration
|
||||
changes become easier.
|
||||
|
||||
Let's upgrade the podinfo Component to see how this works in practice. First,
|
||||
update the Component version field to the latest upstream Helm chart version.
|
||||
|
||||
<Tabs groupId="gen-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos generate component podinfo --component-version 6.6.2
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
generated component
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Remove the cached chart version.
|
||||
|
||||
<Tabs groupId="gen-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
rm -rf components/podinfo/vendor
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Now re-render the Platform.
|
||||
|
||||
<Tabs groupId="render-platform2">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered components/podinfo for cluster east in 327.10475ms
|
||||
rendered components/podinfo for cluster west in 327.796541ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Notice we're still using the upstream chart without modifying it. The Holos
|
||||
component wraps around the chart to mix in additional resources and integrate
|
||||
the component with the broader Platform.
|
||||
|
||||
## Visualize the Changes
|
||||
|
||||
Holos makes it easier to see exactly what changes are made and which resources
|
||||
will be applied to the API server. By design, Holos operates on local files,
|
||||
leaving the task of applying them to ecosystem tools like `kubectl` and ArgoCD.
|
||||
This allows platform operators to inspect changes during code review, or before
|
||||
committing the change at all.
|
||||
|
||||
For example, using `git diff`, we see that the only functional change when
|
||||
upgrading this Helm chart is the deployment of a new container image tag to each
|
||||
cluster. Additionally, we can roll out this change gradually by applying it to
|
||||
the east cluster first, then to the west cluster, limiting the potential blast
|
||||
radius of a problematic change.
|
||||
|
||||
<Tabs groupId="git-diff">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git diff deploy/clusters/east
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```diff showLineNumbers
|
||||
diff --git a/deploy/clusters/east/components/podinfo/podinfo.gen.yaml b/deploy/clusters/east/components/podinfo/podinfo.gen.yaml
|
||||
index 7cc3332..8c1647d 100644
|
||||
--- a/deploy/clusters/east/components/podinfo/podinfo.gen.yaml
|
||||
+++ b/deploy/clusters/east/components/podinfo/podinfo.gen.yaml
|
||||
@@ -5,9 +5,9 @@ kind: Service
|
||||
metadata:
|
||||
name: podinfo
|
||||
labels:
|
||||
- helm.sh/chart: podinfo-6.6.1
|
||||
+ helm.sh/chart: podinfo-6.6.2
|
||||
app.kubernetes.io/name: podinfo
|
||||
- app.kubernetes.io/version: "6.6.1"
|
||||
+ app.kubernetes.io/version: "6.6.2"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
type: ClusterIP
|
||||
@@ -29,9 +29,9 @@ kind: Deployment
|
||||
metadata:
|
||||
name: podinfo
|
||||
labels:
|
||||
- helm.sh/chart: podinfo-6.6.1
|
||||
+ helm.sh/chart: podinfo-6.6.2
|
||||
app.kubernetes.io/name: podinfo
|
||||
- app.kubernetes.io/version: "6.6.1"
|
||||
+ app.kubernetes.io/version: "6.6.2"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
replicas: 1
|
||||
@@ -53,7 +53,7 @@ spec:
|
||||
terminationGracePeriodSeconds: 30
|
||||
containers:
|
||||
- name: podinfo
|
||||
# highlight-next-line
|
||||
- image: "ghcr.io/stefanprodan/podinfo:6.6.1"
|
||||
# highlight-next-line
|
||||
+ image: "ghcr.io/stefanprodan/podinfo:6.6.2"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- ./podinfo
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
Holos is designed to surface the _fully rendered_ manifests intended for the
|
||||
Kubernetes API server, making it easier to see and reason about platform-wide
|
||||
configuration changes.
|
||||
:::
|
||||
|
||||
## Recap {#recap}
|
||||
|
||||
In this quickstart guide, we learned how Holos makes it easier, safer, and more
|
||||
consistent to manage a Platform composed of multiple Clusters and upstream Helm
|
||||
charts.
|
||||
|
||||
We covered how to:
|
||||
|
||||
1. Generate a Git repository for the Platform config.
|
||||
2. Wrap the unmodified upstream podinfo Helm chart into a Component.
|
||||
3. Render an individual Component.
|
||||
4. Mix-in your Platform's unique resources to all Components. For example, ArgoCD Application resources.
|
||||
5. Define multiple similar, but not identical, workload clusters.
|
||||
6. Render the manifests for the entire Platform with the `holos render platform` command.
|
||||
7. Upgrade a Helm chart to the latest version as an important Day 2 task.
|
||||
8. Visualize and surface the details of planned changes Platform wide.
|
||||
|
||||
## Dive Deeper
|
||||
|
||||
If you'd like to dive deeper, check out the [Schema API][schema] and [Core
|
||||
API][core] reference docs. The main difference between the schema and core
|
||||
packages is that the schema is used by users to write refined CUE, while the
|
||||
core package is what the schema produces for `holos` to execute. Users rarely
|
||||
need to interact with the Core API when on the happy path, but can use the core
|
||||
package as an escape hatch when the happy path doesn't go where you want.
|
||||
|
||||
|
||||
[application]: https://argo-cd.readthedocs.io/en/stable/user-guide/application-specification/
|
||||
[schema]: /docs/api/author/v1alpha3/
|
||||
[core]: /docs/api/core/v1alpha3/
|
||||
106
doc/md/archive/guides/2024-09-17-manage-a-project.mdx
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
description: Self service platform resource management for project teams.
|
||||
slug: /archive/guides/2024-09-17-manage-a-project
|
||||
sidebar_position: 250
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Admonition from '@theme/Admonition';
|
||||
|
||||
# Manage a Project
|
||||
|
||||
In this guide we'll explore how Holos easily, safely, and consistently manages
|
||||
platform resources for teams to develop the projects they're working on.
|
||||
|
||||
Intended Audience: Platform Engineers and Software Engineers.
|
||||
|
||||
Goal is to demonstrate how the platform team can consistently, easily, and
|
||||
safely provide platform resources to software engineers.
|
||||
|
||||
Assumption is software engineers have a container they want to deploy onto the
|
||||
platform and make accessible. We'll use httpbin as a stand-in for the dev
|
||||
team's container.
|
||||
|
||||
Project is roughly equivalent to Dev Team for the purpose of this guide, but in
|
||||
practice multiple teams work on a given project over the lifetime of the
|
||||
project, so we structure the files into projects instead of teams.
|
||||
|
||||
## What you'll need {#requirements}
|
||||
|
||||
You'll need the following tools installed to complete this guide.
|
||||
|
||||
1. [holos](/docs/install) - to build the Platform.
|
||||
2. [helm](https://helm.sh/docs/intro/install/) - to render Helm Components.
|
||||
3. [kubectl](https://kubernetes.io/docs/tasks/tools/) - to render Kustomize Components.
|
||||
|
||||
If you'd like to apply the manifests we render in this guide complete the
|
||||
following optional, but recommended, steps.
|
||||
|
||||
a. Complete the [Local Cluster] guide to set up a local cluster to work with.
|
||||
b. You'll need a GitHub account to fork the repository associated with this
|
||||
guide.
|
||||
|
||||
## Fork the Guide Repository
|
||||
|
||||
<Tabs groupId="fork">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This guide assumes you will run commands from the root directory of this
|
||||
repository unless stated otherwise.
|
||||
|
||||
[Quickstart]: /docs/quickstart
|
||||
[Local Cluster]: /docs/guides/local-cluster
|
||||
|
||||
## Render the Platform
|
||||
|
||||
So we can build the basic platform. Don't dwell on the platform bits.
|
||||
|
||||
## Apply the Manifests
|
||||
|
||||
Deploy ArgoCD, but not any of the Application resources.
|
||||
|
||||
## Browse to ArgoCD
|
||||
|
||||
Note there is nothing here yet.
|
||||
|
||||
## Switch to your Fork
|
||||
|
||||
Note all of the Applications change consistently.
|
||||
|
||||
## Apply the Applications
|
||||
|
||||
Note how ArgoCD takes over management, no longer need to k apply.
|
||||
|
||||
## Create a Project
|
||||
|
||||
Project is a conceptual, not technical, thing in Holos. Mainly about how components are laid out in the filesystem tree.
|
||||
|
||||
We use a schematic built into holos as an example, the platform team could use the same or provide a similar template and instructions for development teams to self-serve.
|
||||
|
||||
## Render the Platform
|
||||
|
||||
Notice:
|
||||
|
||||
1. Project is registered with the platform at the root.
|
||||
2. HTTPRoute and Namespace resources are added close to the root in `projects`
|
||||
3. Deployment and Service resources are added at the leaf in `projects/httpbin/backend`
|
||||
|
||||
## Update the image tag
|
||||
|
||||
Add a basic schematic to demonstrate this. May need to add two new flags for image url and image tag to the generate subcommand, but should just be two new fields on the struct.
|
||||
|
||||
## Dive Deeper
|
||||
|
||||
Set the stage for constraints. Ideas: Limit what resources can be added,
|
||||
namespaces can be operated in, enforce labels, etc...
|
||||
|
||||
Simple, consistent, easy constraints.
|
||||
1862
doc/md/archive/guides/expose-a-service.mdx
Normal file
@@ -2,4 +2,28 @@ import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Guides
|
||||
|
||||
## Technical Overview
|
||||
|
||||
Please see the [Technical Overview] to learn about Holos. If you're ready to
|
||||
drive in and try Holos, please work through the following guides.
|
||||
|
||||
## Bank of Holos
|
||||
The guides are organized as a progression. We'll use Holos to manage a
|
||||
fictional bank's platform, the Bank of Holos in each of the guides. In doing so
|
||||
we'll take the time to explain the foundational concepts of Holos.
|
||||
|
||||
1. [Quickstart] covers the foundational concepts of Holos.
|
||||
2. [Deploy a Service] explains how to deploy a containerized service using an
|
||||
existing Helm chart. This guide then explains how to deploy a similar service
|
||||
safely and consistently with CUE instead of Helm.
|
||||
3. [Change a Service] covers the day two task of making configuration changes to
|
||||
deployed services safely and consistently.
|
||||
|
||||
---
|
||||
|
||||
<DocCardList />
|
||||
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[Deploy a Service]: /docs/guides/deploy-a-service/
|
||||
[Change a Service]: /docs/guides/change-a-service/
|
||||
[Technical Overview]: /docs/technical-overview/
|
||||
|
||||
714
doc/md/guides/change-a-service.mdx
Normal file
@@ -0,0 +1,714 @@
|
||||
---
|
||||
description: Change a service on your platform.
|
||||
slug: /guides/change-a-service
|
||||
sidebar_position: 300
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Admonition from '@theme/Admonition';
|
||||
|
||||
# Change a Service
|
||||
|
||||
In this guide, we'll explore how Holos supports the frontend development team at [Bank of Holos] in reconfiguring an already deployed service. Along the way, we'll demonstrate how simple configuration changes are made safer with type checking, and how rendering the complete platform provides clear visibility into those changes.
|
||||
|
||||
This guide builds on the concepts covered in the [Quickstart] and [Deploy a Service] guides.
|
||||
|
||||
## What you'll need {#requirements}
|
||||
|
||||
Like our other guides, this guide is intended to be useful without needing to
|
||||
run each command. If you'd like to apply the manifests to a real Cluster,
|
||||
complete the [Local Cluster Guide](/docs/guides/local-cluster) before this
|
||||
guide.
|
||||
|
||||
You'll need the following tools installed to run the commands in this guide.
|
||||
|
||||
1. [holos](/docs/install) - to build the Platform.
|
||||
2. [helm](https://helm.sh/docs/intro/install/) - to render Holos Components that
|
||||
wrap Helm charts.
|
||||
3. [kubectl](https://kubernetes.io/docs/tasks/tools/) - to render Holos
|
||||
Components that render with Kustomize.
|
||||
|
||||
## Fork the Git Repository
|
||||
|
||||
If you haven't already done so, [fork the Bank of
|
||||
Holos](https://github.com/holos-run/bank-of-holos/fork) then clone the
|
||||
repository to your local machine.
|
||||
|
||||
<Tabs groupId="git-clone">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
# Change YourName
|
||||
git clone https://github.com/YourName/bank-of-holos
|
||||
cd bank-of-holos
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
Cloning into 'bank-of-holos'...
|
||||
remote: Enumerating objects: 1177, done.
|
||||
remote: Counting objects: 100% (1177/1177), done.
|
||||
remote: Compressing objects: 100% (558/558), done.
|
||||
remote: Total 1177 (delta 394), reused 1084 (delta 303), pack-reused 0 (from 0)
|
||||
Receiving objects: 100% (1177/1177), 2.89 MiB | 6.07 MiB/s, done.
|
||||
Resolving deltas: 100% (394/394), done.
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Run the rest of the commands in this guide from the root of the repository.
|
||||
|
||||
If you plan to apply the changes we make, you can delete and re-create your
|
||||
local platform synced to the start of this guide.
|
||||
|
||||
```bash
|
||||
./scripts/reset-cluster
|
||||
./scripts/apply
|
||||
```
|
||||
|
||||
## Rename the Bank
|
||||
|
||||
Let's imagine the bank recently re-branded from The Bank of Holos to The
|
||||
Holistic Bank. The software development team responsible for the front end
|
||||
website needs to update the branding accordingly.
|
||||
|
||||
Let's explore how Holos catches errors early, before they land in production,
|
||||
then guides the team to the best place to make a change.
|
||||
|
||||
The bank front end web service is managed by the
|
||||
`projects/bank-of-holos/frontend/components/bank-frontend/` component which
|
||||
refers to the organization display name in `schema.gen.cue`.
|
||||
|
||||
<Tabs groupId="F5B546EB-566F-4B83-84C3-C55B40F55555">
|
||||
<TabItem value="schema.gen.cue" label="schema.gen.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
import api "github.com/holos-run/holos/api/author/v1alpha3"
|
||||
|
||||
// Define the default organization name
|
||||
// highlight-next-line
|
||||
#Organization: DisplayName: string | *"Bank of Holos"
|
||||
#Organization: Name: string | *"bank-of-holos"
|
||||
|
||||
#Organization: api.#OrganizationStrict
|
||||
#Platform: api.#Platform
|
||||
#Fleets: api.#StandardFleets
|
||||
|
||||
_ComponentConfig: {
|
||||
Resources: #Resources
|
||||
ArgoConfig: #ArgoConfig
|
||||
}
|
||||
|
||||
#Helm: api.#Helm & _ComponentConfig
|
||||
#Kustomize: api.#Kustomize & _ComponentConfig
|
||||
#Kubernetes: api.#Kubernetes & _ComponentConfig
|
||||
|
||||
#ArgoConfig: api.#ArgoConfig & {
|
||||
ClusterName: _ClusterName
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="projects/bank-of-holos/frontend/components/bank-frontend/bank-frontend.cue" label="projects/bank-of-holos/frontend/components/bank-frontend/bank-frontend.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-frontend"
|
||||
Namespace: #BankOfHolos.Frontend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests/frontend.yaml
|
||||
Resources: {
|
||||
Service: frontend: {
|
||||
metadata: name: "frontend"
|
||||
metadata: labels: {
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
spec: {
|
||||
selector: {
|
||||
app: "frontend"
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
_ports: http: {
|
||||
name: "http"
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
protocol: "TCP"
|
||||
}
|
||||
ports: [for x in _ports {x}]
|
||||
}
|
||||
}
|
||||
|
||||
Deployment: frontend: {
|
||||
metadata: name: "frontend"
|
||||
metadata: labels: {
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
app: "frontend"
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
app: "frontend"
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
spec: {
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
}
|
||||
serviceAccountName: "bank-of-holos"
|
||||
terminationGracePeriodSeconds: 5
|
||||
containers: [{
|
||||
env: [{
|
||||
name: "BANK_NAME"
|
||||
// highlight-next-line
|
||||
value: #Organization.DisplayName
|
||||
}, {
|
||||
name: "ENV_PLATFORM"
|
||||
value: "local"
|
||||
}, {
|
||||
name: "VERSION"
|
||||
value: "v0.6.5"
|
||||
}, {
|
||||
name: "PORT"
|
||||
value: "8080"
|
||||
}, {
|
||||
name: "ENABLE_TRACING"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "SCHEME"
|
||||
value: "https"
|
||||
}, {
|
||||
name: "LOG_LEVEL"
|
||||
value: "info"
|
||||
}, {
|
||||
name: "DEFAULT_USERNAME"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_LOGIN_USERNAME"
|
||||
name: "demo-data-config"
|
||||
}
|
||||
}, {
|
||||
name: "DEFAULT_PASSWORD"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_LOGIN_PASSWORD"
|
||||
name: "demo-data-config"
|
||||
}
|
||||
}, {
|
||||
name: "REGISTERED_OAUTH_CLIENT_ID"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_OAUTH_CLIENT_ID"
|
||||
name: "oauth-config"
|
||||
optional: true
|
||||
}
|
||||
}, {
|
||||
name: "ALLOWED_OAUTH_REDIRECT_URI"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_OAUTH_REDIRECT_URI"
|
||||
name: "oauth-config"
|
||||
optional: true
|
||||
}
|
||||
}]
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "service-api-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/frontend:v0.6.5@sha256:d72050f70d12383e4434ad04d189b681dc625f696087ddf0b5df641645c9dafa"
|
||||
livenessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 30
|
||||
}
|
||||
name: "front"
|
||||
readinessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "100m"
|
||||
memory: "64Mi"
|
||||
}
|
||||
}
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["all"]
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/tmp"
|
||||
name: "tmp"
|
||||
}, {
|
||||
mountPath: "/tmp/.ssh"
|
||||
name: "publickey"
|
||||
readOnly: true
|
||||
}]
|
||||
}]
|
||||
volumes: [
|
||||
{
|
||||
emptyDir: {}
|
||||
name: "tmp"
|
||||
},
|
||||
{
|
||||
name: "publickey"
|
||||
secret: {
|
||||
items: [{key: "jwtRS256.key.pub", path: "publickey"}]
|
||||
secretName: "jwt-key"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow HTTPRoutes in the ingress gateway namespace to reference Services
|
||||
// in this namespace.
|
||||
ReferenceGrant: grant: #ReferenceGrant & {
|
||||
metadata: namespace: Namespace
|
||||
}
|
||||
|
||||
// Include shared resources
|
||||
#BankOfHolos.Resources
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Line 6 of the `schema.gen.cue` file defines the _default_ value for
|
||||
`#Organization.DisplayName` by using `string | *"..."`. In CUE, the `*`
|
||||
asterisk character denotes a [default value].
|
||||
|
||||
Line 78 of the `bank-frontend.cue` file refers to `#Organization.DisplayName` to
|
||||
configure the front end web container.
|
||||
|
||||
Let's change the name of the bank by defining a new value for
|
||||
`#Organization.DisplayName` at the root of the configuration. Create
|
||||
`projects/organization.cue` with the following content.
|
||||
|
||||
<Tabs groupId="B386181F-EBE7-469D-8CB5-37631067669B">
|
||||
<TabItem value="projects/organization.cue" label="projects/organization.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
#Organization: DisplayName: "The Holistic-Bank"
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Let's render the platform and see if this changes the name.
|
||||
|
||||
<Tabs groupId="A014333C-3271-4C22-87E6-2B7BF898EA3E">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
#Organization.DisplayName: 2 errors in empty disjunction:
|
||||
#Organization.DisplayName: conflicting values "Bank of Holos" and "The Holistic-Bank":
|
||||
/bank-of-holos/projects/organization.cue:3:29
|
||||
/bank-of-holos/schema.gen.cue:6:39
|
||||
// highlight-next-line
|
||||
#Organization.DisplayName: invalid value "The Holistic-Bank" (out of bound =~"^[0-9A-Za-z][0-9A-Za-z ]{2,61}[0-9A-Za-z]$"):
|
||||
/bank-of-holos/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha3/definitions_go_gen.cue:203:25
|
||||
/bank-of-holos/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha3/definitions_go_gen.cue:188:15
|
||||
/bank-of-holos/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha3/definitions_go_gen.cue:203:15
|
||||
/bank-of-holos/projects/organization.cue:3:29
|
||||
/bank-of-holos/schema.gen.cue:6:29
|
||||
could not run: could not render component: exit status 1 at internal/render/platform.go:50
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::warning Whoops
|
||||
The development team defined a value that isn't allowed by the
|
||||
configuration.
|
||||
:::
|
||||
|
||||
Someone else in the organization placed a [constraint] on the
|
||||
configuration to ensure the display name contains only letters, numbers, and
|
||||
spaces. This constraint is expressed as a [regular expression].
|
||||
|
||||
:::tip
|
||||
CUE provides clear visibility where to start looking to resolve conflicts. Each
|
||||
file and line number listed is a place the `#Organization.DisplayName` field is
|
||||
defined.
|
||||
:::
|
||||
|
||||
Let's try again, this time replacing the hyphen with a space.
|
||||
|
||||
<Tabs groupId="F93B34FA-C0C6-4793-A32F-DAD094403208">
|
||||
<TabItem value="projects/organization.cue" label="projects/organization.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
#Organization: DisplayName: "The Holistic Bank"
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="5FD68778-476A-4F82-8817-71CEE205216E">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered bank-ledger-db for cluster workload in 139.863625ms
|
||||
rendered bank-accounts-db for cluster workload in 151.74875ms
|
||||
rendered bank-balance-reader for cluster workload in 154.356083ms
|
||||
rendered bank-ledger-writer for cluster workload in 161.209541ms
|
||||
rendered bank-userservice for cluster workload in 163.373417ms
|
||||
rendered bank-backend-config for cluster workload in 179.271208ms
|
||||
rendered bank-secrets for cluster workload in 204.35625ms
|
||||
rendered gateway for cluster workload in 118.707583ms
|
||||
rendered httproutes for cluster workload in 140.981541ms
|
||||
rendered bank-transaction-history for cluster workload in 156.066875ms
|
||||
rendered bank-frontend for cluster workload in 300.102292ms
|
||||
rendered bank-contacts for cluster workload in 159.89625ms
|
||||
rendered cni for cluster workload in 150.754458ms
|
||||
rendered istiod for cluster workload in 222.922625ms
|
||||
rendered app-projects for cluster workload in 118.422792ms
|
||||
rendered ztunnel for cluster workload in 142.840625ms
|
||||
rendered cert-manager for cluster workload in 190.938834ms
|
||||
rendered base for cluster workload in 340.679416ms
|
||||
rendered local-ca for cluster workload in 107.120334ms
|
||||
rendered external-secrets for cluster workload in 145.020834ms
|
||||
rendered argocd for cluster workload in 299.690917ms
|
||||
rendered namespaces for cluster workload in 115.862334ms
|
||||
rendered gateway-api for cluster workload in 225.783833ms
|
||||
rendered external-secrets-crds for cluster workload in 339.741166ms
|
||||
rendered crds for cluster workload in 421.849041ms
|
||||
rendered platform in 718.015959ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip Success
|
||||
Great, the platform rendered. We know the display name is valid according to
|
||||
the constraints.
|
||||
:::
|
||||
|
||||
Let's see if the new display name value updated the configuration for the bank
|
||||
frontend.
|
||||
|
||||
<Tabs groupId="6C068651-2061-4262-BE1E-7BB3E7EB66CB">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
On branch main
|
||||
Your branch and 'jeffmccune/main' have diverged,
|
||||
and have 2 and 4 different commits each, respectively.
|
||||
(use "git pull" to merge the remote branch into yours)
|
||||
|
||||
Changes not staged for commit:
|
||||
(use "git add <file>..." to update what will be committed)
|
||||
(use "git restore <file>..." to discard changes in working directory)
|
||||
// highlight-next-line
|
||||
modified: deploy/clusters/workload/components/app-projects/app-projects.gen.yaml
|
||||
modified: deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
|
||||
Untracked files:
|
||||
(use "git add <file>..." to include in what will be committed)
|
||||
projects/organization.cue
|
||||
|
||||
no changes added to commit (use "git add" and/or "git commit -a")
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="4A20831E-461B-4EDE-8F6E-E73C3AEC12DB">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git diff
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```diff
|
||||
diff --git a/deploy/clusters/workload/components/app-projects/app-projects.gen.yaml b/deploy/clusters/workload/components/app-projects/app-projects.gen.yaml
|
||||
index 7914756..250c660 100644
|
||||
--- a/deploy/clusters/workload/components/app-projects/app-projects.gen.yaml
|
||||
+++ b/deploy/clusters/workload/components/app-projects/app-projects.gen.yaml
|
||||
@@ -9,7 +9,7 @@ spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
- description: Holos managed AppProject for Bank of Holos
|
||||
+ description: Holos managed AppProject for The Holistic Bank
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: '*'
|
||||
@@ -26,7 +26,7 @@ spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
- description: Holos managed AppProject for Bank of Holos
|
||||
+ description: Holos managed AppProject for The Holistic Bank
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: '*'
|
||||
@@ -43,7 +43,7 @@ spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
- description: Holos managed AppProject for Bank of Holos
|
||||
+ description: Holos managed AppProject for The Holistic Bank
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: '*'
|
||||
@@ -60,7 +60,7 @@ spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
- description: Holos managed AppProject for Bank of Holos
|
||||
+ description: Holos managed AppProject for The Holistic Bank
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: '*'
|
||||
diff --git a/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml b/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
index dae6f93..d41516b 100644
|
||||
--- a/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
+++ b/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
@@ -71,7 +71,7 @@ spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: BANK_NAME
|
||||
- value: Bank of Holos
|
||||
+ value: The Holistic Bank
|
||||
- name: ENV_PLATFORM
|
||||
value: local
|
||||
- name: VERSION
|
||||
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::danger
|
||||
The new display name changed the frontend container, but it _also_ affected the
|
||||
app-projects component owned by the platform team.
|
||||
:::
|
||||
|
||||
Submitting a pull request would trigger a code review from the platform
|
||||
engineering team who manages the app-projects component. Let's see how to
|
||||
narrow the change down to limit the scope to the bank's user facing services.
|
||||
All of these services are managed under `projects/bank-of-holos/` Move the
|
||||
`organization.cue` file into this folder to limit the scope of configuration to
|
||||
the the components contained within.
|
||||
|
||||
```bash
|
||||
mv projects/organization.cue projects/bank-of-holos/
|
||||
```
|
||||
|
||||
Render the platform and let's see what changed.
|
||||
|
||||
<Tabs groupId="0FFEC244-B59B-4136-9C82-837985DC2AB8">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered bank-ledger-db for cluster workload in 163.814917ms
|
||||
rendered bank-accounts-db for cluster workload in 163.960208ms
|
||||
rendered bank-userservice for cluster workload in 164.1625ms
|
||||
rendered bank-ledger-writer for cluster workload in 169.185291ms
|
||||
rendered bank-balance-reader for cluster workload in 174.5455ms
|
||||
rendered bank-backend-config for cluster workload in 178.092125ms
|
||||
rendered bank-secrets for cluster workload in 202.305334ms
|
||||
rendered gateway for cluster workload in 122.81725ms
|
||||
rendered httproutes for cluster workload in 134.121084ms
|
||||
rendered bank-contacts for cluster workload in 146.4185ms
|
||||
rendered bank-frontend for cluster workload in 311.35425ms
|
||||
rendered bank-transaction-history for cluster workload in 160.103ms
|
||||
rendered cni for cluster workload in 145.762083ms
|
||||
rendered istiod for cluster workload in 216.0065ms
|
||||
rendered app-projects for cluster workload in 117.684333ms
|
||||
rendered ztunnel for cluster workload in 144.555292ms
|
||||
rendered cert-manager for cluster workload in 178.247917ms
|
||||
rendered base for cluster workload in 336.679ms
|
||||
rendered external-secrets for cluster workload in 142.21825ms
|
||||
rendered local-ca for cluster workload in 101.249ms
|
||||
rendered argocd for cluster workload in 280.54525ms
|
||||
rendered namespaces for cluster workload in 106.822042ms
|
||||
rendered gateway-api for cluster workload in 200.459791ms
|
||||
rendered external-secrets-crds for cluster workload in 470.125833ms
|
||||
rendered crds for cluster workload in 844.388666ms
|
||||
rendered platform in 1.154937084s
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="DE4FEEE5-FC53-48A6-BC6F-D0EA1DBFD00C">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git diff
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```diff
|
||||
diff --git a/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml b/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
index dae6f93..d41516b 100644
|
||||
--- a/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
+++ b/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
@@ -71,7 +71,7 @@ spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: BANK_NAME
|
||||
- value: Bank of Holos
|
||||
+ value: The Holistic Bank
|
||||
- name: ENV_PLATFORM
|
||||
value: local
|
||||
- name: VERSION
|
||||
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip Success
|
||||
Great! This time, the only manifest affected is our `bank-frontend.gen.yaml`.
|
||||
:::
|
||||
|
||||
The `BANK_NAME` environment variable will change as we expect, and only the dev
|
||||
teams managing the bank services components are affected by the change.
|
||||
|
||||
Let's commit and push this change and see if it works.
|
||||
|
||||
<Tabs groupId="435D9C60-F841-4CF1-A947-506422E6BAC9">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m 'frontend: rename bank to The Holistic Bank'
|
||||
git push
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main fda74ec] frontend: rename bank to The Holistic Bank
|
||||
2 files changed, 4 insertions(+), 1 deletion(-)
|
||||
create mode 100644 projects/bank-of-holos/organization.cue
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Now that we've pushed the change, let's apply the change to the platform.
|
||||
|
||||
## Apply the Change
|
||||
|
||||
Once we've pushed the change, navigate to the [bank-frontend GitOps
|
||||
Application](https://argocd.holos.localhost/applications/argocd/bank-frontend?view=tree&resource=).
|
||||
We can see the Deployment needs to sync to the desired state we just pushed.
|
||||
|
||||

|
||||
|
||||
Clicking on the frontend Deployment, we see the diff with the change we expect.
|
||||
|
||||

|
||||
|
||||
Sync the change, ArgoCD applies the desired configuration state to the cluster
|
||||
and Kubernetes handles rolling out the updated Deployment resource.
|
||||
|
||||

|
||||
|
||||
Soon, the deployment finishes and the component is in sync again.
|
||||
|
||||

|
||||
|
||||
Finally, let's see if the name actually changed on the website. Navigate to
|
||||
https://bank.holos.localhost/.
|
||||
|
||||

|
||||
|
||||
:::tip Success
|
||||
We successfully made our change and successfully applied the changed
|
||||
configuration to the platform.
|
||||
:::
|
||||
|
||||
Thanks for taking the time to work through this guide which covered:
|
||||
|
||||
- How multiple teams could be impacted by defining configuration at the
|
||||
`projects/` path.
|
||||
- How to scope our change to only affect components within the
|
||||
`projects/bank-of-holos/` path, eliminating the impact on other teams.
|
||||
- How CUE can [constrain] values in Holos, increasing safety.
|
||||
- How to handle a [default value] in CUE.
|
||||
- How CUE surfaces the file and line number of _every_ place to look for where a
|
||||
value is defined, making it faster and easier to troubleshoot problems.
|
||||
|
||||
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[Deploy a Service]: /docs/guides/deploy-a-service/
|
||||
[Change a Service]: /docs/guides/change-a-service/
|
||||
[Helm]: /docs/api/author/v1alpha3/#Helm
|
||||
[Kubernetes]: /docs/api/author/v1alpha3/#Kubernetes
|
||||
[Kustomize]: /docs/api/author/v1alpha3/#Kustomize
|
||||
[ComponentFields]: /docs/api/author/v1alpha3/#ComponentFields
|
||||
[platform-files]: /docs/quickstart/#how-platform-rendering-works
|
||||
[AppProject]: https://argo-cd.readthedocs.io/en/stable/user-guide/projects/
|
||||
[unification operator]: https://cuelang.org/docs/reference/spec/#unification
|
||||
[code-owners]: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
[Kustomization API]: https://github.com/kubernetes-sigs/kustomize/blob/release-kustomize-v5.2/api/types/kustomization.go#L34
|
||||
[cue import]: https://cuelang.org/docs/reference/command/cue-help-import/
|
||||
[cue get go]: https://cuelang.org/docs/concept/how-cue-works-with-go/
|
||||
[timoni-crds]: https://timoni.sh/cue/module/custom-resources/
|
||||
[HTTPRoute]: https://gateway-api.sigs.k8s.io/api-types/httproute/?h=filter
|
||||
[Ingress]: https://kubernetes.io/docs/concepts/services-networking/ingress/
|
||||
[hidden field]: https://cuelang.org/docs/tour/references/hidden/
|
||||
[comprehension]: https://cuelang.org/docs/reference/spec/#comprehensions
|
||||
[code owners]: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
[ReferenceGrant]: https://gateway-api.sigs.k8s.io/api-types/referencegrant/
|
||||
[Local Cluster Guide]: /docs/guides/local-cluster
|
||||
[Bank of Holos]: https://github.com/holos-run/bank-of-holos
|
||||
[default value]: https://cuelang.org/docs/tour/types/defaults/
|
||||
[constrain]: https://cuelang.org/docs/tour/basics/constraints/
|
||||
[constraint]: https://cuelang.org/docs/tour/basics/constraints/
|
||||
[regular expression]: https://cuelang.org/docs/tour/expressions/regexp/
|
||||
1450
doc/md/guides/deploy-a-service.mdx
Normal file
@@ -1,799 +0,0 @@
|
||||
---
|
||||
description: Use Holos to expose a Service with the Gateway API.
|
||||
slug: /guides/expose-a-service
|
||||
sidebar_position: 200
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Admonition from '@theme/Admonition';
|
||||
|
||||
# Expose a Service
|
||||
|
||||
In this guide, you'll learn how to expose a service with Holos using the Gateway
|
||||
API.
|
||||
|
||||
The [Concepts](/docs/concepts) page defines capitalized terms such as Platform
|
||||
and Component.
|
||||
|
||||
## What you'll need {#requirements}
|
||||
|
||||
You'll need the following tools installed to complete this guide.
|
||||
|
||||
1. [holos](/docs/install) - to build the Platform.
|
||||
2. [helm](https://helm.sh/docs/intro/install/) - to render Helm Components.
|
||||
3. [kubectl](https://kubernetes.io/docs/tasks/tools/) - to render Kustomize Components.
|
||||
|
||||
Optionally, if you'd like to apply the rendered manifests to a real Cluster,
|
||||
first complete the [localhost Guide](../local-cluster).
|
||||
|
||||
## Create a Git Repository
|
||||
|
||||
Start by initializing an empty Git repository. Holos operates on local files
|
||||
stored in a Git repository.
|
||||
|
||||
<Tabs groupId="init">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
mkdir expose-a-service
|
||||
cd expose-a-service
|
||||
git init
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
Initialized empty Git repository in /expose-a-service/.git/
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This guide assumes you will run commands from the root directory of the Git
|
||||
repository unless stated otherwise.
|
||||
|
||||
## Generate the Platform {#Generate-Platform}
|
||||
|
||||
Start by generating a platform with one workload Cluster. The `guide` Platform
|
||||
is intended as a starting point for all of our guides.
|
||||
|
||||
<Tabs groupId="generate-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos generate platform guide
|
||||
holos generate component workload-cluster
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
generated component
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Commit the generated platform config to the repository.
|
||||
|
||||
<Tabs groupId="commit-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "holos generate platform guide - $(holos --version)"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
[main (root-commit) 0b17b7f] holos generate platform guide - 0.93.3
|
||||
213 files changed, 72349 insertions(+)
|
||||
...
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Gateway API
|
||||
|
||||
The Gateway API is an official Kubernetes project focused on L4 and L7 routing .
|
||||
You'll use the custom resources defined by the Gateway API to expose the httpbin
|
||||
service outside of the cluster. The Kubernetes Gateway API does not come
|
||||
installed by default on most Kubernetes clusters, so we need to manage the
|
||||
custom resource definitions (CRDs).
|
||||
|
||||
Run the following command to generate a Component to manage the Gateway API.
|
||||
|
||||
<Tabs groupId="gen-gateway-api">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos generate component gateway-api
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
generated component
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The command generates two main configuration files, one at the leaf, and another
|
||||
at the root of the tree. At the leaf, the config produces a Kustomize build
|
||||
plan for Holos to render. At the root, the config adds the Component to all
|
||||
Clusters in the Platform.
|
||||
|
||||
Notice the `kustomization.yaml` file at the leaf. This is an unmodified
|
||||
upstream copy of the standard way to install the Gateway API.
|
||||
|
||||
<Tabs groupId="gateway-api-files">
|
||||
<TabItem value="components/gateway-api/gateway-api.cue" label="Leaf">
|
||||
`components/gateway-api/gateway-api.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Produce a kubectl kustomize build plan.
|
||||
(#Kustomize & {Name: "gateway-api"}).Output
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="kustomization.yaml" label="kustomization.yaml">
|
||||
`components/gateway-api/kustomization.yaml`
|
||||
```yaml showLineNumbers
|
||||
resources:
|
||||
- standard/gateway.networking.k8s.io_gatewayclasses.yaml
|
||||
- standard/gateway.networking.k8s.io_gateways.yaml
|
||||
- standard/gateway.networking.k8s.io_grpcroutes.yaml
|
||||
- standard/gateway.networking.k8s.io_httproutes.yaml
|
||||
- standard/gateway.networking.k8s.io_referencegrants.yaml
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="gateway-api.gen.cue" label="Root">
|
||||
`gateway-api.gen.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Manage on every Cluster in the Platform
|
||||
for Fleet in #Fleets {
|
||||
for Cluster in Fleet.clusters {
|
||||
#Platform: Components: "\(Cluster.name)/gateway-api": {
|
||||
path: "components/gateway-api"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Render the Platform to render the Component for the workload clusters.
|
||||
|
||||
<Tabs groupId="render-platform-gateway">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
rendered components/gateway-api for cluster workload in 279.312292ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
This example is equivalent to running `kubectl kustomize
|
||||
./components/gateway-api` and saving the output to a file. Holos simplifies
|
||||
this task and makes it consistent with Helm and other tools.
|
||||
:::
|
||||
|
||||
Add and commit the Component and rendered Platform.
|
||||
|
||||
<Tabs groupId="commit-gateway-api">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "add gateway-api component"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
[main 88575a5] add gateway-api component
|
||||
9 files changed, 26907 insertions(+)
|
||||
create mode 100644 components/gateway-api/gateway-api.cue
|
||||
create mode 100644 components/gateway-api/kustomization.yaml
|
||||
create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_gatewayclasses.yaml
|
||||
create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_gateways.yaml
|
||||
create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_grpcroutes.yaml
|
||||
create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_httproutes.yaml
|
||||
create mode 100644 components/gateway-api/standard/gateway.networking.k8s.io_referencegrants.yaml
|
||||
create mode 100644 deploy/clusters/workload/components/gateway-api/gateway-api.gen.yaml
|
||||
create mode 100644 gateway-api.gen.cue
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Optionally apply the rendered component to your cluster.
|
||||
|
||||
<Tabs groupId="apply-gateway-api">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
kubectl apply --server-side=true -f deploy/clusters/workload/components/gateway-api
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/grpcroutes.gateway.networking.k8s.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io serverside-applied
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Namespaces
|
||||
|
||||
We often need to manage namespaces prior to workloads being deployed. This is
|
||||
necessary because a namespace is a security boundary. Holos makes it easier,
|
||||
safer, and more consistent to manage service accounts, role bindings, and
|
||||
secrets prior to deploying workloads into a namespace.
|
||||
|
||||
We'll see how this works with the namespaces component, which offers a mechanism
|
||||
for other components to register their namespaces. The namespaces component
|
||||
initializes each registered namespace, optionally mixing in resources
|
||||
consistently.
|
||||
|
||||
Run the following command to generate the namespaces component.
|
||||
|
||||
<Tabs groupId="gen-namespaces">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos generate component namespaces
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
generated component
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The command generates two main configuration files like we've seen with other
|
||||
components. One file at the leaf, and another at the root. The leaf uses a
|
||||
Kubernetes build plan to produce resources directly from CUE.
|
||||
|
||||
<Tabs groupId="namespaces-files">
|
||||
<TabItem value="components/namespaces/namespaces.cue" label="Leaf">
|
||||
`components/namespaces/namespaces.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
let Objects = {
|
||||
Name: "namespaces"
|
||||
// highlight-next-line
|
||||
Resources: Namespace: #Namespaces
|
||||
}
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="namespaces.gen.cue" label="Root">
|
||||
`namespaces.gen.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
// #Namespaces defines all managed namespaces in the Platform.
|
||||
// Holos adopts the sig-multicluster position of namespace sameness.
|
||||
#Namespaces: {
|
||||
// Validate against v1 of the kubernetes core api
|
||||
// highlight-next-line
|
||||
[Name=string]: corev1.#Namespace & {
|
||||
metadata: name: Name
|
||||
}
|
||||
}
|
||||
|
||||
// Manage the Component on every Cluster in the Platform
|
||||
for Fleet in #Fleets {
|
||||
for Cluster in Fleet.clusters {
|
||||
#Platform: Components: "\(Cluster.name)/namespaces": {
|
||||
path: "components/namespaces"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Notice the highlighted line in the leaf file. Resources are managed directly in
|
||||
CUE at the leaf using the Kubernetes component. This is the same mechanism used
|
||||
to mix-in resources to Helm and Kustomize components. The leaf refers to
|
||||
`#Namespaces` defined at the root. At the root `#Namespaces` enforces a
|
||||
constraint: each Namespace must conform to the `k8s.io/api/core/v1`
|
||||
specification.
|
||||
|
||||
:::important
|
||||
We've covered three kinds of components so far. The [Quickstart] guide
|
||||
introduced Helm. We've used Kustomize and Kubernetes in this guide.
|
||||
|
||||
Holos offers a consistent way to manage these different kinds of packaging
|
||||
safely and easily.
|
||||
:::
|
||||
|
||||
- At the **leaf** Holos tailors the component to your platform, mixing
|
||||
in resources and customizing the rendered output.
|
||||
- At the **root** Holos integrates a component with the rest of your platform.
|
||||
|
||||
You'll see this pattern again and again as you build your platform.
|
||||
|
||||
Render the platform to render the component for the workload clusters.
|
||||
|
||||
<Tabs groupId="render-platform-gateway">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
rendered components/namespaces for cluster workload in 72.675292ms
|
||||
rendered components/gateway-api for cluster workload in 259.174583ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Add and commit the configuration and rendered manifests.
|
||||
|
||||
<Tabs groupId="commit-gateway-api">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "add namespaces component"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
[main 1bf0d61] add namespaces component
|
||||
3 files changed, 30 insertions(+)
|
||||
create mode 100644 components/namespaces/namespaces.cue
|
||||
create mode 100644 deploy/clusters/workload/components/namespaces/namespaces.gen.yaml
|
||||
create mode 100644 namespaces.gen.cue
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
`#Namespaces` is currently empty, so the rendered output of
|
||||
`namespaces.gen.yaml` is also empty.
|
||||
|
||||
:::tip
|
||||
Namespaces will be automatically managed as we add more components to the
|
||||
platform over time.
|
||||
:::
|
||||
|
||||
## Istio
|
||||
|
||||
We'll manage Istio to implement the Gateway API so we can expose the httpbin
|
||||
service outside of the cluster.
|
||||
|
||||
Run the following command to generate the istio components.
|
||||
|
||||
<Tabs groupId="gen-istio">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos generate component istio
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
generated component
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::important
|
||||
Mix in the `istio-k3d` component if you're applying the rendered manifests to
|
||||
k3d as described in our [Local Cluster] guide.
|
||||
:::
|
||||
|
||||
Skip this step if you aren't using k3d. Istio needs to be configured to refer
|
||||
to the nonstandard cni configuration paths k3d uses.
|
||||
|
||||
<Tabs groupId="gen-istio-k3d">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos generate component istio-k3d
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
generated component
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="file" label="istio-k3d.gen.cue">
|
||||
Holos makes it easier and safer to mix-in this additional configuration at the root.
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// If you are using k3d with the default Flannel CNI, you must append some
|
||||
// values to your installation command, as k3d uses nonstandard locations for
|
||||
// CNI configuration and binaries.
|
||||
//
|
||||
// See https://istio.io/latest/docs/ambient/install/platform-prerequisites/#k3d
|
||||
#Istio: Values: cni: {
|
||||
cniConfDir: "/var/lib/rancher/k3s/agent/etc/cni/net.d"
|
||||
cniBinDir: "/bin"
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Consistent with the other components we've seen, the istio components define
|
||||
configuration at the root and leafs of the tree. Unlike previous components
|
||||
we've generated, this command generated multiple components to manage Istio.
|
||||
|
||||
<Tabs groupId="tree-istio">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
tree components/istio
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
components/istio
|
||||
├── base
|
||||
│ ├── istio-base.cue
|
||||
│ └── values.gen.cue
|
||||
├── cni
|
||||
│ ├── cni.cue
|
||||
│ └── values.gen.cue
|
||||
├── gateway
|
||||
│ ├── gateway.cue
|
||||
│ └── values.gen.cue
|
||||
├── istiod
|
||||
│ ├── istiod.cue
|
||||
│ └── values.gen.cue
|
||||
└── ztunnel
|
||||
├── values.gen.cue
|
||||
└── ztunnel.cue
|
||||
|
||||
6 directories, 10 files
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
These components share the configuration defined at the root in `istio.gen.cue`.
|
||||
|
||||
Let's review how Holos makes it safer and easier to share Helm values defined at
|
||||
the root with the istiod and cni components defined at the leaf.
|
||||
|
||||
1. istiod and cni use version `"1.23.1"` and namespace `"istio-system"` defined at
|
||||
the root.
|
||||
2. The Helm value to configure ambient (sidecar-less) mode is defined once at
|
||||
the root.
|
||||
3. The root adds a constraint to fail validation if the istio system namespace
|
||||
is not `"istio-system"`. Future upgrades are safer with this constraint, if the
|
||||
upstream vendor changes the default in the future the component will fail
|
||||
validation.
|
||||
4. The root registers two Namespaces, `"istio-system"` and `"istio-ingress"`.
|
||||
5. The root manages the components on all workload clusters in the platform.
|
||||
|
||||
<Tabs groupId="istio-files">
|
||||
<TabItem value="istiod.cue" label="istiod">
|
||||
Leaf `components/istio/istiod/istiod.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
|
||||
let Chart = {
|
||||
Name: "istiod"
|
||||
// highlight-next-line
|
||||
Version: #Istio.Version
|
||||
// highlight-next-line
|
||||
Namespace: #Istio.System.Namespace
|
||||
|
||||
Chart: chart: name: "istiod"
|
||||
|
||||
Repo: name: "istio"
|
||||
Repo: url: "https://istio-release.storage.googleapis.com/charts"
|
||||
|
||||
// highlight-next-line
|
||||
Values: #Istio.Values
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="cni.cue" label="cni">
|
||||
Leaf `components/istio/cni/cni.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
|
||||
let Chart = {
|
||||
Name: "istio-cni"
|
||||
// highlight-next-line
|
||||
Version: #Istio.Version
|
||||
// highlight-next-line
|
||||
Namespace: #Istio.System.Namespace
|
||||
|
||||
Chart: chart: name: "cni"
|
||||
|
||||
Repo: name: "istio"
|
||||
Repo: url: "https://istio-release.storage.googleapis.com/charts"
|
||||
|
||||
// highlight-next-line
|
||||
Values: #Istio.Values
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="istio.gen.cue" label="Root">
|
||||
Root `istio.gen.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// #Istio represents platform wide configuration
|
||||
#Istio: {
|
||||
// highlight-next-line
|
||||
Version: "1.23.1"
|
||||
// highlight-next-line
|
||||
System: Namespace: "istio-system"
|
||||
Gateway: Namespace: "istio-ingress"
|
||||
|
||||
// Constrain Helm values for safer, easier upgrades and consistency across
|
||||
// platform components.
|
||||
// highlight-next-line
|
||||
Values: global: istioNamespace: System.Namespace
|
||||
|
||||
// Configure ambient mode
|
||||
// highlight-next-line
|
||||
Values: profile: "ambient"
|
||||
}
|
||||
|
||||
// Register the Namespaces
|
||||
#Namespaces: (#Istio.System.Namespace): _
|
||||
#Namespaces: (#Istio.Gateway.Namespace): _
|
||||
|
||||
// Manage istio on workload clusters
|
||||
for Cluster in #Fleets.workload.clusters {
|
||||
#Platform: Components: {
|
||||
"\(Cluster.name)/istio-base": {
|
||||
path: "components/istio/base"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/istiod": {
|
||||
path: "components/istio/istiod"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/istio-cni": {
|
||||
path: "components/istio/cni"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/istio-ztunnel": {
|
||||
path: "components/istio/ztunnel"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
"\(Cluster.name)/istio-gateway": {
|
||||
path: "components/istio/gateway"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
Many software projects managed by Holos are organized into a collection of
|
||||
components working together, for example to safely manage custom resource
|
||||
definitions, secrets, and policy separately from the workloads that rely on
|
||||
them.
|
||||
:::
|
||||
|
||||
Render the platform to render the istio components for the workload clusters.
|
||||
|
||||
<Tabs groupId="render-platform-istio">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
rendered components/namespaces for cluster workload in 85.490833ms
|
||||
rendered components/istio/ztunnel for cluster workload in 111.784667ms
|
||||
rendered components/istio/cni for cluster workload in 112.362417ms
|
||||
rendered components/istio/base for cluster workload in 113.058ms
|
||||
rendered components/istio/gateway for cluster workload in 119.018208ms
|
||||
rendered components/istio/istiod for cluster workload in 127.736334ms
|
||||
rendered components/gateway-api for cluster workload in 181.922333ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Add and commit the configuration and rendered manifests.
|
||||
|
||||
<Tabs groupId="commit-istio">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "add istio"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
[main aca8ff6] add istio
|
||||
18 files changed, 18955 insertions(+)
|
||||
create mode 100644 components/istio/base/istio-base.cue
|
||||
create mode 100644 components/istio/base/values.gen.cue
|
||||
create mode 100644 components/istio/cni/cni.cue
|
||||
create mode 100644 components/istio/cni/values.gen.cue
|
||||
create mode 100644 components/istio/gateway/gateway.cue
|
||||
create mode 100644 components/istio/gateway/values.gen.cue
|
||||
create mode 100644 components/istio/istiod/istiod.cue
|
||||
create mode 100644 components/istio/istiod/values.gen.cue
|
||||
create mode 100644 components/istio/ztunnel/values.gen.cue
|
||||
create mode 100644 components/istio/ztunnel/ztunnel.cue
|
||||
create mode 100644 deploy/clusters/workload/components/istio-base/istio-base.gen.yaml
|
||||
create mode 100644 deploy/clusters/workload/components/istio-cni/istio-cni.gen.yaml
|
||||
create mode 100644 deploy/clusters/workload/components/istio-gateway/istio-gateway.gen.yaml
|
||||
create mode 100644 deploy/clusters/workload/components/istio-ztunnel/istio-ztunnel.gen.yaml
|
||||
create mode 100644 deploy/clusters/workload/components/istiod/istiod.gen.yaml
|
||||
create mode 100644 istio-k3d.gen.cue
|
||||
create mode 100644 istio.gen.cue
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Optionally apply the rendered component to your cluster.
|
||||
|
||||
<Tabs groupId="apply-istio-namespaces">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
kubectl apply --server-side=true -f deploy/clusters/workload/components/namespaces
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
namespace/istio-ingress serverside-applied
|
||||
namespace/istio-system serverside-applied
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="apply-istio-base">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
kubectl apply --server-side=true -f deploy/clusters/workload/components/istio-base
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
customresourcedefinition.apiextensions.k8s.io/wasmplugins.extensions.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/destinationrules.networking.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/envoyfilters.networking.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/gateways.networking.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/proxyconfigs.networking.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/serviceentries.networking.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/sidecars.networking.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/virtualservices.networking.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/workloadentries.networking.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/workloadgroups.networking.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/authorizationpolicies.security.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/peerauthentications.security.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/requestauthentications.security.istio.io serverside-applied
|
||||
customresourcedefinition.apiextensions.k8s.io/telemetries.telemetry.istio.io serverside-applied
|
||||
serviceaccount/istio-reader-service-account serverside-applied
|
||||
validatingwebhookconfiguration.admissionregistration.k8s.io/istiod-default-validator serverside-applied
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="apply-istiod">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
kubectl apply --server-side=true -f deploy/clusters/workload/components/istiod
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
poddisruptionbudget.policy/istiod serverside-applied
|
||||
serviceaccount/istiod serverside-applied
|
||||
configmap/istio serverside-applied
|
||||
configmap/istio-sidecar-injector serverside-applied
|
||||
clusterrole.rbac.authorization.k8s.io/istiod-clusterrole-istio-system serverside-applied
|
||||
clusterrole.rbac.authorization.k8s.io/istiod-gateway-controller-istio-system serverside-applied
|
||||
clusterrole.rbac.authorization.k8s.io/istio-reader-clusterrole-istio-system serverside-applied
|
||||
clusterrolebinding.rbac.authorization.k8s.io/istiod-clusterrole-istio-system serverside-applied
|
||||
clusterrolebinding.rbac.authorization.k8s.io/istiod-gateway-controller-istio-system serverside-applied
|
||||
clusterrolebinding.rbac.authorization.k8s.io/istio-reader-clusterrole-istio-system serverside-applied
|
||||
role.rbac.authorization.k8s.io/istiod serverside-applied
|
||||
rolebinding.rbac.authorization.k8s.io/istiod serverside-applied
|
||||
service/istiod serverside-applied
|
||||
deployment.apps/istiod serverside-applied
|
||||
horizontalpodautoscaler.autoscaling/istiod serverside-applied
|
||||
mutatingwebhookconfiguration.admissionregistration.k8s.io/istio-sidecar-injector serverside-applied
|
||||
validatingwebhookconfiguration.admissionregistration.k8s.io/istio-validator-istio-system serverside-applied
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="apply-istio-cni">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
kubectl apply --server-side=true -f deploy/clusters/workload/components/istio-cni
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
serviceaccount/istio-cni serverside-applied
|
||||
configmap/istio-cni-config serverside-applied
|
||||
clusterrole.rbac.authorization.k8s.io/istio-cni serverside-applied
|
||||
clusterrole.rbac.authorization.k8s.io/istio-cni-repair-role serverside-applied
|
||||
clusterrole.rbac.authorization.k8s.io/istio-cni-ambient serverside-applied
|
||||
clusterrolebinding.rbac.authorization.k8s.io/istio-cni serverside-applied
|
||||
clusterrolebinding.rbac.authorization.k8s.io/istio-cni-repair-rolebinding serverside-applied
|
||||
clusterrolebinding.rbac.authorization.k8s.io/istio-cni-ambient serverside-applied
|
||||
daemonset.apps/istio-cni-node serverside-applied
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="apply-istio-ztunnel">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
kubectl apply --server-side=true -f deploy/clusters/workload/components/istio-ztunnel
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
serviceaccount/ztunnel serverside-applied
|
||||
daemonset.apps/ztunnel serverside-applied
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="apply-istio-gateway">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
kubectl apply --server-side=true -f deploy/clusters/workload/components/istio-gateway
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
serviceaccount/gateway serverside-applied
|
||||
role.rbac.authorization.k8s.io/gateway serverside-applied
|
||||
rolebinding.rbac.authorization.k8s.io/gateway serverside-applied
|
||||
service/gateway serverside-applied
|
||||
deployment.apps/gateway serverside-applied
|
||||
horizontalpodautoscaler.autoscaling/gateway serverside-applied
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Make sure all pod containers become ready.
|
||||
|
||||
<Tabs groupId="apply-istio-ready">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
kubectl get pods -A
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt showLineNumbers
|
||||
NAMESPACE NAME READY STATUS RESTARTS AGE
|
||||
istio-ingress gateway-6748c5f547-s46pj 1/1 Running 0 26s
|
||||
istio-system istio-cni-node-852nr 1/1 Running 0 2m9s
|
||||
istio-system istiod-5b4d8d4c77-t694z 1/1 Running 0 3m15s
|
||||
istio-system ztunnel-msqbn 1/1 Running 0 63s
|
||||
kube-system coredns-576bfc4dc7-2g4k9 1/1 Running 0 113m
|
||||
kube-system local-path-provisioner-6795b5f9d8-wsz8p 1/1 Running 0 113m
|
||||
kube-system metrics-server-557ff575fb-fctr7 1/1 Running 0 113m
|
||||
kube-system svclb-gateway-5d311af0-fp5mk 3/3 Running 0 26s
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Once all pods are ready, we're ready to manage httpbin so we can route http
|
||||
traffic to it.
|
||||
|
||||
## httpbin
|
||||
|
||||
[Quickstart]: /docs/quickstart
|
||||
[Local Cluster]: /docs/guides/local-cluster
|
||||
15
doc/md/guides/helm-component.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
description: Helm Component
|
||||
slug: /guides/helm-component
|
||||
sidebar_position: 400
|
||||
---
|
||||
|
||||
# Helm Component
|
||||
|
||||
The [Deploy a Service](/docs/guides/deploy-a-service/) guide is the best guide
|
||||
we have on wrapping a Helm chart in a Holos Component. The [Helm] section of
|
||||
the Author API may also be useful.
|
||||
|
||||
[Helm]: /docs/api/author/v1alpha3/#Helm
|
||||
[Kubernetes]: /docs/api/author/v1alpha3/#Kubernetes
|
||||
[Kustomize]: /docs/api/author/v1alpha3/#Kustomize
|
||||
BIN
doc/md/guides/img/bank-home.png
Normal file
|
After Width: | Height: | Size: 690 KiB |
BIN
doc/md/guides/img/change-a-service-diff.png
Normal file
|
After Width: | Height: | Size: 997 KiB |
BIN
doc/md/guides/img/change-a-service-in-sync.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
doc/md/guides/img/change-a-service-login-page.png
Normal file
|
After Width: | Height: | Size: 287 KiB |
BIN
doc/md/guides/img/change-a-service-out-of-sync.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
doc/md/guides/img/change-a-service-progressing.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
doc/md/guides/img/deploy-a-service-podinfo-deployed.png
Normal file
|
After Width: | Height: | Size: 1009 KiB |
BIN
doc/md/guides/img/deploy-a-service-podinfo-greetings.png
Normal file
|
After Width: | Height: | Size: 617 KiB |
BIN
doc/md/guides/img/deploy-a-service-podinfo-missing.png
Normal file
|
After Width: | Height: | Size: 706 KiB |
BIN
doc/md/guides/img/deploy-a-service-podinfo-sync.png
Normal file
|
After Width: | Height: | Size: 794 KiB |
20
doc/md/guides/kubernetes-component.mdx
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
description: Kubernetes Component
|
||||
slug: /guides/kubernetes-component
|
||||
sidebar_position: 500
|
||||
---
|
||||
|
||||
# Kubernetes Component
|
||||
|
||||
:::warning
|
||||
TODO
|
||||
:::
|
||||
|
||||
This is a placeholder for a guide for managing Kubernetes resources directly
|
||||
from a Holos Component with strong type checking.
|
||||
|
||||
In the meantime, please refer to the [Kubernetes] section of the Author API.
|
||||
|
||||
[Helm]: /docs/api/author/v1alpha3/#Helm
|
||||
[Kubernetes]: /docs/api/author/v1alpha3/#Kubernetes
|
||||
[Kustomize]: /docs/api/author/v1alpha3/#Kustomize
|
||||
20
doc/md/guides/kustomize-component.mdx
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
description: Wrap a Kustomize Kustomization in a Holos Component.
|
||||
slug: /guides/kustomize-component
|
||||
sidebar_position: 600
|
||||
---
|
||||
|
||||
# Kustomize Component
|
||||
|
||||
:::warning
|
||||
TODO
|
||||
:::
|
||||
|
||||
This is a placeholder for a guide on wrapping a Kustomize Kustomization base
|
||||
with a Holos component.
|
||||
|
||||
In the meantime, please refer to the [Kustomize] section of the Author API.
|
||||
|
||||
[Helm]: /docs/api/author/v1alpha3/#Helm
|
||||
[Kubernetes]: /docs/api/author/v1alpha3/#Kubernetes
|
||||
[Kustomize]: /docs/api/author/v1alpha3/#Kustomize
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: Build a local Cluster to use with these guides.
|
||||
slug: /guides/local-cluster
|
||||
sidebar_position: 300
|
||||
sidebar_position: 999
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
@@ -88,7 +88,7 @@ Finally, add your trusted certificate authority.
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
kubectl apply --server-side=true -f "$(mkcert -CAROOT)/namespace.yaml"
|
||||
kubectl apply --server-side=true -f "$(mkcert -CAROOT)/local-ca.yaml"
|
||||
kubectl apply --server-side=true -n cert-manager -f "$(mkcert -CAROOT)/local-ca.yaml"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
⚡️ Holos will help you build your **software development platform in no time.**
|
||||
|
||||
💸 Building a software development platform is **time consuming and expensive**. Spend more time building features for your customers and less time managing your development platform.
|
||||
|
||||
💥 Already have a platform? Add new features and services to your platform easily with Holos.
|
||||
|
||||
🧐 Holos is a platform builder. It builds a hollistic software development platform composed of best-of-breed cloud native open source projects. Holos is also a tool to make it easier to manage cloud infrastructure by providing a typed alternative to yaml templates.
|
||||
|
||||
## Features
|
||||
|
||||
Holos was built to solve two main problems:
|
||||
|
||||
1. Building a platform usually takes 3 engineers 6-9 months of effort. Holos provides a reference platform that enables you to deploy and customize your platform in a fraction of the time.
|
||||
2. Configuration changes often cause outages. Existing tools like Helm make it difficult to understand the impact a configuration change will have. Holos provides a unique, unified configuration model powered by CUE that makes it safer and easier to roll out configuration changes.
|
||||
|
||||
A core principle of Holos is that organizations gain value from owning the the platform they build on. Avoid vendor lock-in, future price hikes, and expensive licensing changes by building on a solid foundation of open source, cloud native computing foundation backed projects.
|
||||
|
||||
The following features are built into the Holos reference platform.
|
||||
|
||||
:::tip
|
||||
|
||||
Don't see your preferred technology in the stack? Holos is designed to enable you to swap out components of the platform tech stack.
|
||||
|
||||
:::
|
||||
|
||||
- **Continuous Delivery**
|
||||
- Holos builds a GitOps workflow for each application running in the platform.
|
||||
- Developers push changes which are automatically deployed.
|
||||
- Powered by [ArgoCD](https://argo-cd.readthedocs.io/en/stable/)
|
||||
- **Identity and Access Management** (IAM)
|
||||
- Holos builds a standard OIDC identity provider for you.
|
||||
- Integrates with your exisitng IAM and SSO system, or works independently.
|
||||
- Powerful customer identity and access management features.
|
||||
- Role based access control.
|
||||
- Powered by [ZITADEL](https://zitadel.com/)
|
||||
- **Zero Trust**
|
||||
- Authenticate and Authorize users at the platform layer instead of or in addition to the application layer.
|
||||
- Integrated with observability to measure and alert about problems before customers complain.
|
||||
- Powered by [Istio](https://istio.io/)
|
||||
- **Observability**
|
||||
- Holos collects performance and availability metrics automatically, without requiring application changes.
|
||||
- Optional, deeper integration into the application layer.
|
||||
- Distributed Tracing
|
||||
- Logging
|
||||
- Powered by Prometheus, Grafana, Loki, and OpenTelemetry.
|
||||
- **Data Platform**
|
||||
- Integrated management of PostgreSQL
|
||||
- Automatic backups
|
||||
- Automatic restore from backup
|
||||
- Quickly fail over across multiple regions
|
||||
- **Multi-Region**
|
||||
- Holos is designed to operate in multiple regions and multiple clouds.
|
||||
- Keep customer data in the region that makes the most sense for your business.
|
||||
- Easily cut over from one region to another for redundancy and business continuity.
|
||||
|
||||
## Development Status
|
||||
|
||||
Holos is being actively developed by [Open Infrastructure Services](https://openinfrastructure.co). Release can be found [here](https://github.com/holos-run/holos/releases).
|
||||
|
||||
## Adoption
|
||||
|
||||
Organizations who have officially adopted Holos can be found [here](https://github.com/holos-run/holos/blob/main/ADOPTERS.md).
|
||||
@@ -5,6 +5,49 @@ slug: /
|
||||
|
||||
# Introduction
|
||||
|
||||
:::warning TODO
|
||||
See [introduction](https://github.com/facebook/docusaurus/blob/main/website/docs/introduction.mdx?plain=1)
|
||||
:::
|
||||
Welcome to Holos. Holos is an open source tool to manage software development
|
||||
platforms safely, easily, and consistently. We built Holos to help engineering
|
||||
teams work more efficiently together by empowering them to build golden paths
|
||||
and paved roads for other teams to leverage for quicker delivery.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Guides] are organized into example use-cases of how Holos helps engineering
|
||||
teams at the fictional Bank of Holos deliver business value on the bank's
|
||||
platform.
|
||||
- The [API Reference] is a technical reference for when you're writing CUE code to define your platform.
|
||||
|
||||
## Backstory
|
||||
|
||||
At [Open Infrastructure Services], we've each helped dozens of companies build and operate their software development platforms. During the U.S. presidential election just before the pandemic, our second-largest client, Twitter, experienced a global outage that lasted nearly a full day. We were managing their production configuration system, allowing the core infrastructure team to focus on business-critical objectives. This gave us a front-row seat to the incident.
|
||||
|
||||
A close friend and engineer on the team made a trivial one-line change to the firewall configuration. Less than 30 minutes later, everything was down. That change, which passed code review, caused the host firewall to revert to its default state on hundreds of thousands of servers, blocking all connections globally—except for SSH, thankfully. Even a Presidential candidate complained loudly.
|
||||
|
||||
This incident forced us to reconsider key issues with Twitter's platform:
|
||||
|
||||
1. **Lack of Visibility** - Engineers couldn't foresee the impact of even a small change, making it difficult to assess risks.
|
||||
2. **Large Blast Radius** - Small changes affected the entire global fleet in under 30 minutes. There was no way to limit the impact of a single change.
|
||||
3. **Incomplete Tooling** - The right processes were in place, but the tooling didn't fully support them. The change was tested and reviewed, but critical information wasn't surfaced in time.
|
||||
|
||||
Over the next few years, we built features to address these issues. Meanwhile, I began exploring how these solutions could work in the Kubernetes and cloud-native space.
|
||||
|
||||
As Google Cloud partners, we worked with large customers to understand how they built their platforms on Kubernetes. During the pandemic, we built a platform using CNCF projects like ArgoCD, Prometheus Stack, Istio, Cert Manager, and External Secrets Operator, integrating them into a cohesive platform. We started with upstream recommendations—primarily Helm charts—and wrote scripts to integrate each piece into the platform. For example, we passed Helm outputs to Kustomize to add labels or fix bugs, and wrote umbrella charts to add Ingress, HTTPRoute, and ExternalSecret resources.
|
||||
|
||||
These scripts served as necessary glue to hold everything together but became difficult to manage across multiple environments, regions, and cloud providers. YAML templates and nested loops created friction, making them hard to troubleshoot. The scripts themselves made it difficult to see what was happening and to fix issues affecting the entire platform.
|
||||
|
||||
Still, the scripts had a key advantage: they produced fully rendered manifests in plain text, committed to version control, and applied via ArgoCD. This clarity made troubleshooting easier and reduced errors in production.
|
||||
|
||||
Despite the makeshift nature of the scripts, I kept thinking about the "[Why are we templating YAML]?" post on Hacker News. I wanted to replace our scripts and charts with something more robust and easier to maintain—something that addressed Twitter's issues head-on.
|
||||
|
||||
I rewrote our scripts and charts using CUE and Go, replacing the glue layer. The result is **Holos**—a tool designed to complement Helm, Kustomize, and Jsonnet, making it easier and safer to define golden paths and paved roads without bespoke scripts or templates.
|
||||
|
||||
Thanks for reading. Take Holos for a spin on your local machine with our [Quickstart] guide.
|
||||
|
||||
[Guides]: /docs/guides/
|
||||
[API Reference]: /docs/api/
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[CUE]: https://cuelang.org/
|
||||
[Author API]: /docs/api/author/
|
||||
[Core API]: /docs/api/core/
|
||||
[Open Infrastructure Services]: https://openinfrastructure.co/
|
||||
[Why are we templating YAML]: https://hn.algolia.com/?dateRange=all&page=0&prefix=false&query=https%3A%2F%2Fleebriggs.co.uk%2Fblog%2F2019%2F02%2F07%2Fwhy-are-we-templating-yaml&sort=byDate&type=story
|
||||
|
||||
@@ -2,4 +2,12 @@ import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Get Started
|
||||
|
||||
## Start with the [Quickstart] guide
|
||||
|
||||
---
|
||||
|
||||
These documents provide additional context to supplement the [Quickstart] guide.
|
||||
|
||||
<DocCardList />
|
||||
|
||||
[Quickstart]: /docs/quickstart/
|
||||
|
||||
@@ -6,65 +6,10 @@ sidebar_position: 300
|
||||
|
||||
# Comparison
|
||||
|
||||
:::tip
|
||||
Holos is designed to complement and improve, not replace, existing tools in the
|
||||
cloud native ecosystem.
|
||||
:::
|
||||
|
||||
## Helm
|
||||
|
||||
### Chart Users
|
||||
|
||||
Describe how things are different when using an upstream helm chart.
|
||||
|
||||
### Chart Authors
|
||||
|
||||
Describe how things are different when writing a new helm chart.
|
||||
|
||||
## Kustomize
|
||||
|
||||
TODO
|
||||
|
||||
## ArgoCD
|
||||
|
||||
TODO
|
||||
|
||||
## Flux
|
||||
|
||||
TODO
|
||||
|
||||
## Timoni
|
||||
|
||||
| Aspect | Timoni | Holos | Comment |
|
||||
| ---------- | -------------------- | -------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| Language | CUE | CUE | Like Holos, Timoni is also built on CUE. |
|
||||
| Artifact | OCI Image | Plain YAML Files | The Holos Authors find plain files easier to work with and reason about than OCI images. |
|
||||
| Outputs to | OCI Image Repository | Local Git repository | Holos is designed for use with existing GitOps tools. |
|
||||
| Concept | Module | Component | A Timoni Module is analogous to a Holos Component. |
|
||||
| Concept | Bundle | Platform | A Timoni Bundle is somewhat similar, but smaller in scope to a Holos Platform. |
|
||||
|
||||
:::important
|
||||
|
||||
The Holos Authors are deeply grateful to Stefan and Timoni for the capability of
|
||||
importing Kubernetes custom resource definitions into CUE. Without this
|
||||
functionality, much of the Kubernetes ecosystem would be more difficult to
|
||||
manage in CUE and therefore in Holos.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
## KubeVela
|
||||
|
||||
1. Also built on CUE.
|
||||
2. Intended to create an Application abstraction.
|
||||
3. Holos prioritizes composition over abstraction.
|
||||
4. An abstraction of an Application acts as a filter that removes all but the lowest common denominator functionality. The Holos Authors have found this filtering effect to create excessive friction for software developers.
|
||||
5. Holos focuses instead on composition to empower developers and platform engineers to leverage the unique features and functionality of their software and platform.
|
||||
|
||||
## Pulumi
|
||||
|
||||
TODO
|
||||
|
||||
## Jsonnet
|
||||
|
||||
TODO
|
||||
|
||||
@@ -324,28 +324,28 @@ ArgoCD, or Flux.
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Figure 2 - Render Pipeline
|
||||
title: Figure 1 - Render Pipeline
|
||||
---
|
||||
graph LR
|
||||
PS[<a href="/docs/api/core/v1alpha2#PlatformSpec">PlatformSpec</a>]
|
||||
BP[<a href="/docs/api/core/v1alpha2#BuildPlan">BuildPlan</a>]
|
||||
HC[<a href="/docs/api/core/v1alpha2#HolosComponent">Components</a>]
|
||||
PS[<a href="/docs/api/author/v1alpha3/#Platform">Platform</a>]
|
||||
HC[<a href="/docs/api/author/v1alpha3/#ComponentFields">Components</a>]
|
||||
BP[<a href="/docs/api/core/v1alpha3#BuildPlan">BuildPlan</a>]
|
||||
|
||||
H[<a href="/docs/api/core/v1alpha2#HelmChart">HelmChart</a>]
|
||||
K[<a href="/docs/api/core/v1alpha2#KustomizeBuild">KustomizeBuild</a>]
|
||||
O[<a href="/docs/api/core/v1alpha2#KubernetesObjects">KubernetesObjects</a>]
|
||||
H[<a href="/docs/api/author/v1alpha3/#Helm">Helm</a>]
|
||||
K[<a href="/docs/api/author/v1alpha3/#Kustomize">Kustomize</a>]
|
||||
O[<a href="/docs/api/author/v1alpha3/#Kubernetes">Kubernetes</a>]
|
||||
|
||||
P[<a href="/docs/api/core/v1alpha2#Kustomize">Kustomize</a>]
|
||||
P[<a href="/docs/api/core/v1alpha3#Kustomize">Kustomize</a>]
|
||||
Y[Kubernetes <br/>Resources]
|
||||
G[GitOps <br/>Resource]
|
||||
FS[Local Files]
|
||||
|
||||
C[Kube API Server]
|
||||
|
||||
PS --> BP --> HC
|
||||
HC --> H --> P
|
||||
HC --> K --> P
|
||||
HC --> O --> P
|
||||
PS --> HC --> BP
|
||||
BP --> H --> P
|
||||
BP --> K --> P
|
||||
BP --> O --> P
|
||||
|
||||
P --> Y --> FS
|
||||
P --> G --> FS
|
||||
|
||||
23
doc/md/start/support.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
description: Get Support for Holos
|
||||
slug: /support
|
||||
sidebar_position: 900
|
||||
---
|
||||
|
||||
# Support
|
||||
|
||||
## Community Support
|
||||
|
||||
You can ask questions in our community forums in [GitHub Discussions](https://github.com/holos-run/holos/discussions) or [Google Groups](https://groups.google.com/g/holos-discuss).
|
||||
|
||||
## Commercial Support and Services
|
||||
|
||||
### Open Infrastructure Services
|
||||
|
||||
[Open Infrastructure Services] are the primary stewards of Holos. Contact Open
|
||||
Infrastructure Services for training, support, and services related to Holos,
|
||||
platform engineering, and cloud infrastructure automation.
|
||||
|
||||
Please email holos-support@openinfrastructure.co for more information.
|
||||
|
||||
[Open Infrastructure Services]: https://openinfrastructure.co/
|
||||
650
doc/md/technical-overview.md
Normal file
@@ -0,0 +1,650 @@
|
||||
---
|
||||
slug: technical-overview
|
||||
title: Technical Overview
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Admonition from '@theme/Admonition';
|
||||
|
||||
## Overview
|
||||
|
||||
Holos makes it easier for platform teams to integrate software into their
|
||||
platform. Existing tools in the Kubernetes ecosystem are narrowly focused on
|
||||
application management. Holos takes a holistic approach, focusing on the broad
|
||||
integration layer where applications are joined into the platform. Holos
|
||||
improves cross team collaboration through well defined, typed structures at the
|
||||
integration layer. These definitions provide golden paths for other teams to
|
||||
easily integrate their own services into the platform.
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
## The Problem
|
||||
|
||||
Platform teams need to develop and maintain significant glue code to integrate
|
||||
Helm charts and YAML manifests into a platform built on Kubernetes. This glue
|
||||
code is often implemented with home grown umbrella charts and scripts.
|
||||
Maintaining these charts and scripts takes time and effort that could otherwise
|
||||
be spent improving the platform. The need for each organization to develop and
|
||||
maintain this glue code indicates a gap in the Kubernetes ecosystem. Holos is a
|
||||
Go command line tool leveraging [CUE] to fill this gap.
|
||||
|
||||
## Key Features
|
||||
|
||||
1. Holos enables teams to provide simple definitions for other teams to use as golden paths.
|
||||
2. Define integrations in [CUE] with strong type checking. No more text templates or bash scripts.
|
||||
3. Simplify complex integration. Order does not matter. Validation is early and quick.
|
||||
4. Reuse your existing Helm charts and Kustomize bases.
|
||||
5. Implement the [rendered manifests pattern]. Changes are clearly visible platform-wide.
|
||||
6. Fully render manifests to plain files. Use your existing GitOps tools and processes.
|
||||
7. Post-process with Kustomize from CUE instead of plain text files. Customize your Kustomizations.
|
||||
8. Mix in resources to Helm charts and Kustomize bases, for example ExternalSecrets.
|
||||
9. Render all of Helm, Kustomize, CUE, JSON, and YAML consistently with the same process.
|
||||
|
||||
## Rendering Pipeline
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Figure 1 - Render Pipeline
|
||||
---
|
||||
graph LR
|
||||
PS[<a href="/docs/api/author/v1alpha3/#Platform">Platform</a>]
|
||||
HC[<a href="/docs/api/author/v1alpha3/#ComponentFields">Components</a>]
|
||||
BP[<a href="/docs/api/core/v1alpha3#BuildPlan">BuildPlan</a>]
|
||||
|
||||
H[<a href="/docs/api/author/v1alpha3/#Helm">Helm</a>]
|
||||
K[<a href="/docs/api/author/v1alpha3/#Kustomize">Kustomize</a>]
|
||||
O[<a href="/docs/api/author/v1alpha3/#Kubernetes">Kubernetes</a>]
|
||||
|
||||
P[<a href="/docs/api/core/v1alpha3#Kustomize">Kustomize</a>]
|
||||
Y[Kubernetes <br/>Resources]
|
||||
G[GitOps <br/>Resource]
|
||||
FS[Local Files]
|
||||
|
||||
C[Kube API Server]
|
||||
|
||||
PS --> HC --> BP
|
||||
BP --> H --> P
|
||||
BP --> K --> P
|
||||
BP --> O --> P
|
||||
|
||||
P --> Y --> FS
|
||||
P --> G --> FS
|
||||
|
||||
FS --> ArgoCD --> C
|
||||
FS --> Flux --> C
|
||||
FS --> kubectl --> C
|
||||
```
|
||||
|
||||
## Use Case
|
||||
|
||||
One of the development teams at the fictional Bank of Holos wants to deploy a
|
||||
simple web app for an experimental project they're working on.
|
||||
|
||||
The platform team at the bank wants to build a simple golden path for teams to
|
||||
provision projects consistently and easily in compliance with the bank's
|
||||
policies.
|
||||
|
||||
### Platform Team
|
||||
|
||||
The platform team builds a golden path for development teams to register their
|
||||
project with the platform. In compliance with bank policy, the platform team
|
||||
needs to manage important security resources for each new project. All of these
|
||||
resources can be derived from only 3 pieces of information.
|
||||
|
||||
1. The name of the project the dev team is working on.
|
||||
2. The name of the team who currently owns the project.
|
||||
3. The services, if any, the project is exposing.
|
||||
|
||||
The platform team defines a structure for the dev team to register this
|
||||
information. This structure provides the golden path for the dev team.
|
||||
|
||||
The development team registers their experimental project, creatively named
|
||||
"experiment" by submitting a pull request that contains this information.
|
||||
|
||||
<Tabs groupId="EB9C9AF1-F1AA-4189-B746-A5B8E3043F87">
|
||||
<TabItem value="projects/experiment.cue" label="projects/experiment.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// The development team registers a project name.
|
||||
#Projects: experiment: {
|
||||
// The project owner must be named.
|
||||
Owner: Name: "dev-team"
|
||||
// Expose Service podinfo at https://podinfo.example.com
|
||||
Hostnames: podinfo: Port: 9898
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The platform team uses these three pieces of information to derive all of the
|
||||
platform resources necessary to support the development team.
|
||||
|
||||
1. **Namespace** for the project resources.
|
||||
2. **RoleBinding** to grant the dev team access to the project namespace.
|
||||
3. **SecretStore** which implements the secret management policy for the bank.
|
||||
4. **ReferenceGrant** to expose the project services through the Gateway API.
|
||||
5. **HTTPRoutes** to expose the project services, if any.
|
||||
6. **AppProject** to deploy and manage the project Applications with ArgoCD.
|
||||
7. **Common Labels** to ensure every resource is labeled for resource accounting.
|
||||
|
||||
Rendering the platform generates fully rendered manifests for all of these
|
||||
resources. These manifests are derived from the three pieces of information the
|
||||
dev team provided.
|
||||
|
||||
Note the platform team must manage these resources across multiple namespaces.
|
||||
The first four reside in the project namespace owned by the dev team. The
|
||||
HTTPRoute and AppProject go into two namespaces managed by the platform team.
|
||||
Holos makes it easier for the platform team to organize these resources into
|
||||
different components with different owners.
|
||||
|
||||
:::tip
|
||||
Holos supports [CODEOWNERS] by clearly defining the teams responsible for each
|
||||
platform component.
|
||||
:::
|
||||
|
||||
<Tabs groupId="2E46EA1C-B118-44BF-AE20-752E8D1CE131">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered namespaces for cluster overview in 93.024042ms
|
||||
rendered projects for cluster overview in 96.080667ms
|
||||
rendered httproutes for cluster overview in 96.047ms
|
||||
rendered platform in 96.805292ms
|
||||
```
|
||||
:::note
|
||||
If you'd like to try this for yourself, `cd` into [examples/tech-overview] and
|
||||
render the platform.
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The fully rendered manifests are written into the `deploy/` directory organized
|
||||
by cluster and component for GitOps.
|
||||
|
||||
<Tabs groupId="07FBE14E-E9EA-437B-9FA1-C6D8806524AD">
|
||||
<TabItem value="deploy/clusters/overview/components/namespaces/namespaces.gen.yaml" label="namespaces">
|
||||
```yaml showLineNumbers
|
||||
# deploy/clusters/overview/components/namespaces/namespaces.gen.yaml
|
||||
---
|
||||
metadata:
|
||||
name: experiment
|
||||
labels:
|
||||
kubernetes.io/metadata.name: experiment
|
||||
example.com/project.name: experiment
|
||||
example.com/owner.name: dev-team
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
kind: Namespace
|
||||
apiVersion: v1
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="deploy/clusters/overview/components/projects/projects.gen.yaml" label="projects">
|
||||
```yaml showLineNumbers
|
||||
# deploy/clusters/overview/components/projects/projects.gen.yaml
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: istio-ingress
|
||||
namespace: experiment
|
||||
labels:
|
||||
example.com/project.name: experiment
|
||||
example.com/owner.name: dev-team
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: istio-ingress
|
||||
to:
|
||||
- group: ""
|
||||
kind: Service
|
||||
---
|
||||
metadata:
|
||||
name: admin
|
||||
namespace: experiment
|
||||
labels:
|
||||
example.com/project.name: experiment
|
||||
example.com/owner.name: dev-team
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: admin
|
||||
subjects:
|
||||
- apiGroup: rbac.authorization.k8s.io
|
||||
kind: Group
|
||||
name: oidc:sg-dev-team@example.com
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
---
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: default
|
||||
namespace: experiment
|
||||
labels:
|
||||
example.com/project.name: experiment
|
||||
example.com/owner.name: dev-team
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
spec:
|
||||
provider:
|
||||
kubernetes:
|
||||
remoteNamespace: experiment
|
||||
auth:
|
||||
token:
|
||||
bearerToken:
|
||||
key: token
|
||||
name: eso-reader
|
||||
server:
|
||||
url: https://management.example.com:6443
|
||||
caBundle: LS0tLS1CRUd...S0tLS0K
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="deploy/clusters/overview/components/httproutes/httproutes.gen.yaml" label="httproutes">
|
||||
```yaml showLineNumbers
|
||||
# deploy/clusters/overview/components/httproutes/httproutes.gen.yaml
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: podinfo.holos.localhost
|
||||
namespace: istio-ingress
|
||||
labels:
|
||||
example.com/project.name: experiment
|
||||
example.com/owner.name: dev-team
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
spec:
|
||||
hostnames:
|
||||
- podinfo.holos.localhost
|
||||
parentRefs:
|
||||
- name: default
|
||||
namespace: istio-ingress
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: podinfo
|
||||
namespace: experiment
|
||||
port: 9898
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The rendered manifests are derived from the project registration information by
|
||||
definitions implemented by the platform team. The [Author API] provides a
|
||||
[Project] schema, but does not define an implementation. The platform team
|
||||
implements the [Project] schema by writing a `#Projects` definition to manage
|
||||
resources according to bank policies.
|
||||
|
||||
:::important
|
||||
The Author API is intended as a convenient, ergonomic reference for component
|
||||
authors. Definitions **are not** confined to the Author API.
|
||||
:::
|
||||
|
||||
The following example shows how the platform team wrote the `#Projects`
|
||||
definition to derive the Namespace from the project registration provided by the
|
||||
dev team.
|
||||
|
||||
<Tabs groupId="5732727B-295E-46E1-B851-F8A1C5D7DF88">
|
||||
<TabItem value="projects/platform/components/namespaces/namespaces.cue" label="Namespaces Component">
|
||||
```txt
|
||||
projects/platform/components/namespaces/namespaces.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
let Objects = {
|
||||
Name: "namespaces"
|
||||
Resources: Namespace: #Namespaces
|
||||
}
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
```
|
||||
|
||||
1. This is the namespaces component which simply manages all of the namespaces derived from the project registration data shown in the second tab.
|
||||
2. Line 5 manages a Namespace for each value of the `#Namespaces` struct. See the second tab for how the platform team defines this structure.
|
||||
</TabItem>
|
||||
<TabItem value="projects/projects.cue" label="#Projects Definition">
|
||||
```txt
|
||||
projects/projects.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
import api "github.com/holos-run/holos/api/author/v1alpha3"
|
||||
|
||||
// Projects defines the structure other teams register with to manage project
|
||||
// resources. The platform team defines the schema, development teams provide
|
||||
// the values.
|
||||
#Projects: api.#Projects & {
|
||||
[NAME=string]: {
|
||||
Name: NAME
|
||||
// The platform team requires the development teams to indicate an owner of
|
||||
// the project.
|
||||
Owner: Name: string
|
||||
// The default value for the owner email address is derived from the owner
|
||||
// name, but development teams can provide a different email address if
|
||||
// needed.
|
||||
Owner: Email: string | *"sg-\(Owner.Name)@\(#Organization.Domain)"
|
||||
// The platform team constrains the project to a single namespace.
|
||||
Namespaces: close({(NAME): Name: NAME})
|
||||
// The platform team constrains the exposed services to the project
|
||||
// namespace.
|
||||
Hostnames: [HOST=string]: {
|
||||
Name: HOST
|
||||
Namespace: Namespaces[NAME].Name
|
||||
Service: HOST
|
||||
Port: number | *80
|
||||
}
|
||||
|
||||
// CommonLabels is not part of the Projects API, so we use a hidden field to
|
||||
// provide common labels to components that render resources from CUE.
|
||||
_CommonLabels: {
|
||||
"\(#Organization.Domain)/project.name": Name
|
||||
"\(#Organization.Domain)/owner.name": Owner.Name
|
||||
"\(#Organization.Domain)/owner.email": Owner.Email
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for Project in #Projects {
|
||||
// Register project namespaces with the namespaces component.
|
||||
#Namespaces: {
|
||||
for Namespace in Project.Namespaces {
|
||||
(Namespace.Name): metadata: labels: Project._CommonLabels
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. On lines 8-37 the platform team derives most fields from the project name (line 9), and the owner name (line 13). The purpose is to fill in the remaining fields defined by the Author API.
|
||||
2. Line 13 The dev team is expected to provide a concrete owner name, indicated by the `string` value.
|
||||
3. Line 17 The platform team provides a default value for the email address. The project team may define a different value.
|
||||
4. Line 19 The Author API allows a project to have many namespaces. The platform team constrains this down to one namespace per project by closing the struct. The namespace name must be the same as the project name.
|
||||
5. Lines 22-27 The platform team derives values for a Gateway API [BackendObjectReference] from the hostname provided by the project team. These values are used later to build HTTPRoutes to expose their service.
|
||||
6. Lines 31-35 Common labels aren't part of the Author API, so the platform team defines a hidden field to make them available throughout the configuration.
|
||||
7. Lines 39-46 The platform team adds a namespace with common labels for each project to the struct we saw in the first tab.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The RoleBinding, SecretScore, and ReferenceGrant are managed in the
|
||||
[projects](https://github.com/holos-run/bank-of-holos/blob/v0.1.1/examples/tech-overview/projects/platform/components/projects/projects.cue)
|
||||
component, similar to the previous namespaces example.
|
||||
The HTTPRoute is managed separately in the
|
||||
[httproutes](https://github.com/holos-run/bank-of-holos/blob/v0.1.1/examples/tech-overview/projects/platform/components/httproutes/httproutes.cue)
|
||||
component.
|
||||
|
||||
All components are registered with the platform in the
|
||||
[platform](https://github.com/holos-run/bank-of-holos/tree/v0.1.1/examples/tech-overview/platform)
|
||||
directory.
|
||||
|
||||
:::important
|
||||
Multiple components, potentially owned by different teams, derive fully rendered
|
||||
resources from the same three project values. The dev team added these three
|
||||
values to the `#Projects` definition. The platform team wrote the definition to
|
||||
integrate software according to bank policies. CUE powers this _unified_
|
||||
platform configuration model.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
Components map 1:1 to ArgoCD Applications or Flux Kustomizations.
|
||||
:::
|
||||
|
||||
### Development Team
|
||||
|
||||
The development team has the platform resources they need, but they still need
|
||||
to deploy their container. The development team submits a pull request adding
|
||||
the following two files to deploy their existing Helm chart.
|
||||
|
||||
<Tabs groupId="7AD1DDA9-8001-462B-8BE0-D9410EB51233">
|
||||
<TabItem value="projects/experiment/components/podinfo/podinfo.cue" label="Helm Component">
|
||||
```txt
|
||||
projects/experiment/components/podinfo/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).BuildPlan
|
||||
|
||||
let Chart = {
|
||||
Name: "podinfo"
|
||||
Version: "6.6.2"
|
||||
Repo: name: "podinfo"
|
||||
Repo: url: "https://stefanprodan.github.io/podinfo"
|
||||
}
|
||||
```
|
||||
This file represents a Helm chart component to add to the platform. The second
|
||||
tab registers this component with the platform.
|
||||
</TabItem>
|
||||
<TabItem value="platform/podinfo.cue" label="Component Registration">
|
||||
```
|
||||
platform/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Manage the component on every workload Cluster, but not management clusters.
|
||||
for Cluster in #Fleets.workload.clusters {
|
||||
#Platform: Components: "\(Cluster.name)/podinfo": {
|
||||
path: "projects/experiment/components/podinfo"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
```
|
||||
This file registers the component with the platform. When the platform is
|
||||
rendered the dev team's Helm chart will be rendered on all workload clusters
|
||||
across the platform.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Once the dev team's component is registered, rendering the platform will render
|
||||
their component.
|
||||
|
||||
<Tabs groupId="1BAF7AD2-BBCD-4797-A3A6-55A626732845">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered app-projects for cluster overview in 92.087042ms
|
||||
rendered projects for cluster overview in 95.6325ms
|
||||
rendered httproutes for cluster overview in 96.968916ms
|
||||
rendered namespaces for cluster overview in 97.610291ms
|
||||
// highlight-next-line
|
||||
rendered podinfo for cluster overview in 155.410417ms
|
||||
rendered platform in 155.470542ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="77BF500B-105A-4AB4-A615-DEC19F501AE1">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
cat deploy/clusters/overview/components/podinfo/podinfo.gen.yaml
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```yaml showLineNumbers
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/name: podinfo
|
||||
app.kubernetes.io/version: 6.6.2
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
example.com/owner.name: dev-team
|
||||
example.com/project.name: experiment
|
||||
helm.sh/chart: podinfo-6.6.2
|
||||
name: podinfo
|
||||
namespace: experiment
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 9898
|
||||
protocol: TCP
|
||||
targetPort: http
|
||||
- name: grpc
|
||||
port: 9999
|
||||
protocol: TCP
|
||||
targetPort: grpc
|
||||
selector:
|
||||
app.kubernetes.io/name: podinfo
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
example.com/owner.name: dev-team
|
||||
example.com/project.name: experiment
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/name: podinfo
|
||||
app.kubernetes.io/version: 6.6.2
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
example.com/owner.name: dev-team
|
||||
example.com/project.name: experiment
|
||||
helm.sh/chart: podinfo-6.6.2
|
||||
name: podinfo
|
||||
namespace: experiment
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: podinfo
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
example.com/owner.name: dev-team
|
||||
example.com/project.name: experiment
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/port: "9898"
|
||||
prometheus.io/scrape: "true"
|
||||
labels:
|
||||
app.kubernetes.io/name: podinfo
|
||||
example.com/owner.email: sg-dev-team@example.com
|
||||
example.com/owner.name: dev-team
|
||||
example.com/project.name: experiment
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- ./podinfo
|
||||
- --port=9898
|
||||
- --cert-path=/data/cert
|
||||
- --port-metrics=9797
|
||||
- --grpc-port=9999
|
||||
- --grpc-service-name=podinfo
|
||||
- --level=info
|
||||
- --random-delay=false
|
||||
- --random-error=false
|
||||
env:
|
||||
- name: PODINFO_UI_COLOR
|
||||
value: '#34577c'
|
||||
image: ghcr.io/stefanprodan/podinfo:6.6.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- podcli
|
||||
- check
|
||||
- http
|
||||
- localhost:9898/healthz
|
||||
failureThreshold: 3
|
||||
initialDelaySeconds: 1
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 5
|
||||
name: podinfo
|
||||
ports:
|
||||
- containerPort: 9898
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: 9797
|
||||
name: http-metrics
|
||||
protocol: TCP
|
||||
- containerPort: 9999
|
||||
name: grpc
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- podcli
|
||||
- check
|
||||
- http
|
||||
- localhost:9898/readyz
|
||||
failureThreshold: 3
|
||||
initialDelaySeconds: 1
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 5
|
||||
resources:
|
||||
limits: null
|
||||
requests:
|
||||
cpu: 1m
|
||||
memory: 16Mi
|
||||
volumeMounts:
|
||||
- mountPath: /data
|
||||
name: data
|
||||
terminationGracePeriodSeconds: 30
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: data
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Note the rendered Helm chart resources have consistent project labels. The
|
||||
platform team added a constraint to the project so all Helm charts are post
|
||||
processed with Kustomize to add these common labels. The platform team
|
||||
accomplishes this by adding a constraint in the project directory. This can be
|
||||
seen in
|
||||
[experiment/components.cue](https://github.com/holos-run/bank-of-holos/blob/v0.1.1/examples/tech-overview/projects/experiment/components.cue)
|
||||
|
||||
We've covered how the platform team provides a golden path for development teams
|
||||
to register their projects by defining a Projects structure. We've also covered
|
||||
how the development team deploys their existing Helm chart onto the platform.
|
||||
|
||||
## Support & Resources
|
||||
|
||||
1. See our [Quickstart] guide to get started with Holos.
|
||||
2. Check out our other [Guides] which cover specific topics.
|
||||
3. Refer to the [Author API] when writing components.
|
||||
4. Consider the [Core API] if you need to do something more advanced than the Author API supports.
|
||||
5. Community and commercial [Support] is available.
|
||||
6. [Discussions Forum](https://github.com/holos-run/holos/discussions)
|
||||
|
||||
[Support]: /docs/support/
|
||||
[Guides]: /docs/guides/
|
||||
[API Reference]: /docs/api/
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[CUE]: https://cuelang.org/
|
||||
[Author API]: /docs/api/author/
|
||||
[Core API]: /docs/api/core/
|
||||
[Open Infrastructure Services]: https://openinfrastructure.co/
|
||||
[Why are we templating YAML]: https://hn.algolia.com/?dateRange=all&page=0&prefix=false&query=https%3A%2F%2Fleebriggs.co.uk%2Fblog%2F2019%2F02%2F07%2Fwhy-are-we-templating-yaml&sort=byDate&type=story
|
||||
[Holos]: https://holos.run/
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[rendered manifests pattern]: https://akuity.io/blog/the-rendered-manifests-pattern/
|
||||
[examples/tech-overview]: https://github.com/holos-run/bank-of-holos/tree/v0.1.1/examples/tech-overview
|
||||
[BackendObjectReference]: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.BackendObjectReference
|
||||
[CODEOWNERS]: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
[Project]: /docs/api/author/v1alpha3/#Project
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
slug: welcome
|
||||
title: Welcome
|
||||
authors: [jeff]
|
||||
tags: [holos]
|
||||
---
|
||||
|
||||
TODO - Coming Soon
|
||||
94
doc/website/blog/2024-09-24-holos-announcement/index.md
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
slug: holos-platform-manager
|
||||
title: Holos Platform Manager
|
||||
authors: [jeff]
|
||||
tags: [holos]
|
||||
---
|
||||
|
||||
## Introducing Holos
|
||||
|
||||
I’m excited to announce Holos, a tool designed to help engineering teams
|
||||
manage their software development platforms built on the Kubernetes resource
|
||||
model.
|
||||
|
||||
:::tip
|
||||
For a hands-on introduction, check out our [Quickstart] Guide.
|
||||
:::
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
### The Backstory
|
||||
|
||||
In our roles at [Open Infrastructure Services], and earlier at Puppet, we helped
|
||||
many companies automate infrastructure management. In 2017, we had the
|
||||
opportunity to work with Twitter to improve their configuration management
|
||||
system. This opportunity gave us insight into the challenges of managing a
|
||||
large-scale platform with multiple engineering teams. Our work involved
|
||||
everything from observability systems to application deployment workflows and of
|
||||
course, managing the core infrastructure.
|
||||
|
||||
This experience demonstrated the value of platform engineering. As the pandemic
|
||||
hit, I began thinking about what a fully cloud-native platform might look like
|
||||
using the Kubernetes resource model. Around the same time, I came across the
|
||||
Hacker News post, “[Why Are We Templating YAML]?”, which sparked a good
|
||||
discussion. It was clear I wasn’t alone in my frustration with managing YAML
|
||||
files and ensuring clear, predictable changes before merging them into
|
||||
production.
|
||||
|
||||
A common pain point and theme is the complexity of working with nested YAML
|
||||
configurations, especially with tools like ArgoCD and Helm. The lack of a
|
||||
standard for rendering YAML templates makes it difficult to see what changes are
|
||||
actually being applied to the Kubernetes API. This often results in trial and
|
||||
error, costly blue-green deployments, and hours of debugging.
|
||||
|
||||
During the pandemic, I began experimenting with a tool to address this issue,
|
||||
drawing on lessons from our work at Twitter. The key problems we aimed to solve
|
||||
are:
|
||||
|
||||
- **Lack of visibility**: Engineers struggled to foresee the impact of small changes.
|
||||
- **Large blast radius**: Small changes affected global systems, with no way to limit the impact.
|
||||
- **Incomplete tooling**: While processes were in place, the right information wasn’t surfaced at the right time.
|
||||
|
||||
We built several iterations of a reference platform based on Kubernetes,
|
||||
initially focusing on fully rendering manifests into plain files—a pattern now
|
||||
called the [rendered manifests pattern]. Over time, we realized we were spending
|
||||
most of our time maintaining bash scripts and YAML templates. This led back to
|
||||
the question: Why are we templating YAML? What _should_ replace templates?
|
||||
|
||||
We'd previously seen a colleague use CUE effectively to generate large scale
|
||||
configurations for Envoy, and ran into CUE again when we worked on a project
|
||||
involving Dagger, but I still hadn't taken a deep look at CUE.
|
||||
|
||||
At the end of 2023, I decided to dive deep with [CUE]. I quickly came to
|
||||
appreciate CUE’s unified approach where **order is irrelevant**. Before CUE, we
|
||||
handled configuration data in a hierarchy with a precedence ordering, similar to
|
||||
how we handled data in Puppet with Hiera. CUE's promise of no longer needing to
|
||||
think about ordering and precedence rules held, alleviating a large cognitive
|
||||
burden when dealing with complex configurations. CUE quickly allowed me to
|
||||
replace the unmaintainable bash scripts and complex Helm templates, simplifying
|
||||
our workflow.
|
||||
|
||||
### Enter Holos
|
||||
|
||||
Holos adds CUE as a well-specified integration layer over tools like Helm,
|
||||
Kustomize, ArgoCD, and Crossplane. With Holos, we can now efficiently integrate
|
||||
upstream Helm charts and Kustomize bases into our platform without the
|
||||
complexity of templates and scripts. This has also made it easy for one team to
|
||||
define "golden paths" that other teams can follow—like automatically configuring
|
||||
namespaces and security policies when dev teams start new projects.
|
||||
|
||||
We've found Holos incredibly useful and hope you do too. Let us know your
|
||||
thoughts!
|
||||
|
||||
[Guides]: /docs/guides/
|
||||
[API Reference]: /docs/api/
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[CUE]: https://cuelang.org/
|
||||
[Author API]: /docs/api/author/
|
||||
[Core API]: /docs/api/core/
|
||||
[Open Infrastructure Services]: https://openinfrastructure.co/
|
||||
[Why are we templating YAML]: https://hn.algolia.com/?dateRange=all&page=0&prefix=false&query=https%3A%2F%2Fleebriggs.co.uk%2Fblog%2F2019%2F02%2F07%2Fwhy-are-we-templating-yaml&sort=byDate&type=story
|
||||
|
||||
[Holos]: https://holos.run/
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[rendered manifests pattern]: https://akuity.io/blog/the-rendered-manifests-pattern/
|
||||
@@ -1,6 +1,6 @@
|
||||
jeff:
|
||||
name: Jeff McCune
|
||||
title: Holos maintainer & creator
|
||||
title: Holos maintainer & author
|
||||
url: https://github.com/jeffmccune
|
||||
image_url: https://github.com/jeffmccune.png
|
||||
|
||||
|
||||
@@ -2,3 +2,8 @@ holos:
|
||||
label: Holos
|
||||
permalink: /holos
|
||||
description: Holos Platform
|
||||
|
||||
tech:
|
||||
label: Technical
|
||||
permalink: /tech
|
||||
description: Technical Articles
|
||||
|
||||
@@ -4,7 +4,7 @@ import type * as Preset from '@docusaurus/preset-classic';
|
||||
|
||||
const config: Config = {
|
||||
title: 'Holos',
|
||||
tagline: 'The Holistic Package Manager for Cloud Native Applications',
|
||||
tagline: 'An easier way for platform teams to integrate software into their platform',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
// Set the production url of your site here
|
||||
@@ -12,6 +12,7 @@ const config: Config = {
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: '/',
|
||||
// trailing slash is necessary for Cloudflare pages.
|
||||
trailingSlash: true,
|
||||
|
||||
// GitHub pages deployment config.
|
||||
@@ -65,13 +66,17 @@ const config: Config = {
|
||||
blogSidebarTitle: "All posts",
|
||||
feedOptions: {
|
||||
type: 'all',
|
||||
copyright: `Copyright © ${new Date().getFullYear()}, The Holos Authors.`,
|
||||
copyright: `Copyright © ${new Date().getFullYear()}, The Holos Authors`,
|
||||
},
|
||||
showReadingTime: false,
|
||||
},
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
gtag: {
|
||||
trackingID: 'G-M00QMB1N05',
|
||||
anonymizeIP: true,
|
||||
}
|
||||
} satisfies Preset.Options,
|
||||
],
|
||||
],
|
||||
@@ -95,9 +100,10 @@ const config: Config = {
|
||||
type: 'doc',
|
||||
docId: 'guides/quickstart',
|
||||
position: 'left',
|
||||
label: 'Try Holos',
|
||||
label: 'Quickstart',
|
||||
},
|
||||
{ to: '/docs', label: 'Docs', position: 'left' },
|
||||
{ to: '/docs/technical-overview', label: 'Docs', position: 'left' },
|
||||
{ to: '/docs/guides', label: 'Guides', position: 'left' },
|
||||
{
|
||||
type: 'doc',
|
||||
docId: 'api',
|
||||
@@ -106,13 +112,7 @@ const config: Config = {
|
||||
},
|
||||
{ to: '/blog', label: 'Blog', position: 'left' },
|
||||
{
|
||||
"href": "https://pkg.go.dev/github.com/holos-run/holos?tab=doc",
|
||||
"label": "GoDoc",
|
||||
"position": "left",
|
||||
"className": "header-godoc-link",
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/holos-run/holos',
|
||||
href: 'https://github.com/holos-run',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
@@ -146,7 +146,19 @@ const config: Config = {
|
||||
title: 'Community',
|
||||
items: [
|
||||
{
|
||||
label: 'Discuss',
|
||||
label: 'Support',
|
||||
href: '/docs/support',
|
||||
},
|
||||
{
|
||||
label: 'Announcements List',
|
||||
href: 'https://groups.google.com/g/holos-announce',
|
||||
},
|
||||
{
|
||||
label: 'Discussion List',
|
||||
href: 'https://groups.google.com/g/holos-discuss',
|
||||
},
|
||||
{
|
||||
label: 'Discussion Forum',
|
||||
href: 'https://github.com/holos-run/holos/discussions',
|
||||
},
|
||||
],
|
||||
@@ -162,6 +174,10 @@ const config: Config = {
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/holos-run/holos',
|
||||
},
|
||||
{
|
||||
label: 'GoDoc',
|
||||
href: 'https://pkg.go.dev/github.com/holos-run/holos?tab=doc',
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { SidebarsConfig } from '@docusaurus/plugin-content-docs';
|
||||
const sidebars: SidebarsConfig = {
|
||||
doc: [
|
||||
'introduction',
|
||||
'technical-overview',
|
||||
{
|
||||
label: 'Getting Started',
|
||||
type: 'category',
|
||||
@@ -44,14 +45,14 @@ const sidebars: SidebarsConfig = {
|
||||
link: { type: 'doc', id: 'api' },
|
||||
items: [
|
||||
{
|
||||
label: 'Schema API',
|
||||
label: 'Author API',
|
||||
type: 'category',
|
||||
link: { type: 'doc', id: 'api/schema' },
|
||||
link: { type: 'doc', id: 'api/author' },
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
type: 'autogenerated',
|
||||
dirName: 'api/schema',
|
||||
dirName: 'api/author',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
@@ -8,53 +8,56 @@ type FeatureItem = {
|
||||
description: JSX.Element;
|
||||
};
|
||||
|
||||
// TODO: Consider focusing on the three pillars of Safe, Easy, Consistent.
|
||||
// We don't focus on features, but rather problems and solutions.
|
||||
const FeatureList: FeatureItem[] = [
|
||||
{
|
||||
title: 'Kustomize Helm',
|
||||
Svg: require('@site/static/img/base00/undraw_together_re_a8x4.svg').default,
|
||||
title: 'For Platform Engineers',
|
||||
Svg: require('@site/static/img/base00/undraw_software_engineer_re_tnjc.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Super charge your existing Helm charts by providing well defined,
|
||||
validated input values, post-processing the output with Kustomize,
|
||||
and mixing in your own custom resources. All without modifying upstream
|
||||
charts to alleviate the pain of upgrades.
|
||||
<p align="left">
|
||||
<ul>
|
||||
<li>Provide simple definitions for other teams to use as golden paths.</li>
|
||||
<li>Define integrations in <a href="https://cuelang.org/">CUE</a> with strong type checking. No more text templates or bash scripts.</li>
|
||||
<li>Reuse your existing Helm charts and Kustomize bases.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<a href="/docs/technical-overview">Learn More</a>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Unified Data Model',
|
||||
Svg: require('@site/static/img/base00/undraw_fitting_pieces_re_nss7.svg').default,
|
||||
title: 'For Software Developers',
|
||||
Svg: require('@site/static/img/base00/undraw_through_the_park_lxnl.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Unify all of your platform components into one well-defined, strongly
|
||||
typed data model with CUE. Holos makes it easier and safer to integrate
|
||||
seamlessly with software distributed with current and future tools that
|
||||
produce Kubernetes resource manifests.
|
||||
<p align="left">
|
||||
<ul>
|
||||
<li>Move faster using paved paths from your platform and security teams.</li>
|
||||
<li>Develop locally or in the cloud.</li>
|
||||
<li>Spend more time developing software and fewer cycles fighting infrastructure challenges.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<a href="/docs/technical-overview">Learn More</a>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Deep Insights',
|
||||
Svg: require('@site/static/img/base00/undraw_code_review_re_woeb.svg').default,
|
||||
title: 'For Security Teams',
|
||||
Svg: require('@site/static/img/base00/undraw_security_on_re_e491.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Reduce risk and increase confidence in your configuration changes.
|
||||
Holos offers clear visibility into complete resource configuration
|
||||
<i>before</i> being applied.
|
||||
<p align="left">
|
||||
<ul>
|
||||
<li>Define security policy as reusable, typed configurations.</li>
|
||||
<li>Automatically enforce security policy on new projects.</li>
|
||||
<li>Ensure a consistent security posture cross-platform with fewer code changes.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<a href="/docs/technical-overview">Learn More</a>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Interoperable',
|
||||
Svg: require('@site/static/img/base00/undraw_version_control_re_mg66.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Holos is designed for compatibility with your preferred tools and
|
||||
processes, for example git diff and code reviews.
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
function Feature({ title, Svg, description }: FeatureItem) {
|
||||
|
||||
@@ -5,9 +5,40 @@
|
||||
*/
|
||||
|
||||
/* Enable wrapping by default for mobile */
|
||||
pre code {
|
||||
/* pre code {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
} */
|
||||
|
||||
.hero__title {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hero__subtitle {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.projectDesc {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hero__buttons {
|
||||
float: left
|
||||
}
|
||||
|
||||
/* Ensure img in hero banner scales well even on mobile */
|
||||
@media screen and (max-width: 996px) {
|
||||
div.diagramImg {
|
||||
width: 100%;
|
||||
max-width: 100px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
div.diagramImg {
|
||||
width: 30%;
|
||||
min-width: 300px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
|
||||
@@ -12,30 +12,30 @@ function HomepageHeader() {
|
||||
return (
|
||||
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
||||
<div className="container">
|
||||
<div className="diagramImg">
|
||||
<img src="./img/holos-diagram-color-transparent.svg" alt="Holos Diagram" />
|
||||
</div>
|
||||
<Heading as="h1" className="hero__title">
|
||||
{siteConfig.title}
|
||||
</Heading>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<p className="projectDesc">
|
||||
Holos adds CUE's type safety, unified structure, and strong validation
|
||||
features to your current software packages, including Helm and
|
||||
Kustomize. These features make the experience of integrating software
|
||||
packages into a holistic platform a pleasant journey.
|
||||
</p>
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
className="button button--secondary button--lg"
|
||||
to="docs/quickstart">
|
||||
Get Started
|
||||
</Link>
|
||||
<span className={styles.divider}></span>
|
||||
<Link
|
||||
className="button button--primary button--lg"
|
||||
to="docs/concepts">
|
||||
Learn More
|
||||
</Link>
|
||||
<div className="hero__buttons">
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
className="button button--secondary button--lg"
|
||||
to="docs/quickstart">
|
||||
Get Started
|
||||
</Link>
|
||||
<span className={styles.divider}></span>
|
||||
<Link
|
||||
className="button button--primary button--lg"
|
||||
to="docs/technical-overview/">
|
||||
Learn More
|
||||
</Link>
|
||||
<span className={styles.divider}></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
</header >
|
||||
);
|
||||
}
|
||||
@@ -44,8 +44,8 @@ export default function Home(): JSX.Element {
|
||||
const { siteConfig } = useDocusaurusContext();
|
||||
return (
|
||||
<Layout
|
||||
title={`${siteConfig.title} Package Manager`}
|
||||
description="Holos adds CUE's type safety, unified structure, and strong validation features to your current software packages, including Helm and Kustomize.">
|
||||
title={`${siteConfig.title} Platform Manager`}
|
||||
description="Holos adds CUE's type safety, unified structure, and strong validation features to your Kubernetes configuration manifests, including Helm and Kustomize.">
|
||||
<HomepageHeader />
|
||||
<main>
|
||||
<HomepageFeatures />
|
||||
|
||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 461 KiB After Width: | Height: | Size: 480 KiB |
BIN
doc/website/static/img/social-card-blank.png
Normal file
|
After Width: | Height: | Size: 477 KiB |
@@ -4,4 +4,4 @@ set -euo pipefail
|
||||
|
||||
TOPLEVEL="$(cd $(dirname "$0") && git rev-parse --show-toplevel)"
|
||||
|
||||
cd "${TOPLEVEL}" && npx cspell ./doc/md/**/*.{md,mdx,markdown}
|
||||
cd "${TOPLEVEL}" && npx cspell ./doc/md/**/*.{md,mdx,markdown} ./doc/md/*.{md,mdx,markdown}
|
||||
|
||||
32
holos.go
@@ -1,6 +1,8 @@
|
||||
// Package holos defines types for the rest of the system.
|
||||
package holos
|
||||
|
||||
import "context"
|
||||
|
||||
// A PathCueMod is a string representing the absolute filesystem path of a cue
|
||||
// module. It is given a unique type so the API is clear.
|
||||
type PathCueMod string
|
||||
@@ -8,3 +10,33 @@ type PathCueMod string
|
||||
// A InstancePath is a string representing the absolute filesystem path of a
|
||||
// holos instance. It is given a unique type so the API is clear.
|
||||
type InstancePath string
|
||||
|
||||
// FilePath represents the path of a file relative to the current working
|
||||
// directory of holos at runtime.
|
||||
type FilePath string
|
||||
|
||||
// FileContent represents the contents of a file as a string.
|
||||
type FileContent string
|
||||
|
||||
// TypeMeta represents the kind and version of a resource holos needs to
|
||||
// process. Useful to discriminate generated resources.
|
||||
type TypeMeta struct {
|
||||
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||
}
|
||||
|
||||
// Builder builds file artifacts.
|
||||
type Builder interface {
|
||||
Build(context.Context, ArtifactMap) error
|
||||
}
|
||||
|
||||
// ArtifactMap sets and gets data for file artifacts.
|
||||
//
|
||||
// Concrete values must ensure Set is write once, returning an error if a given
|
||||
// FilePath was previously Set. Concrete values must be safe for concurrent
|
||||
// reads and writes.
|
||||
type ArtifactMap interface {
|
||||
Get(path string) (data []byte, ok bool)
|
||||
Set(path string, data []byte) error
|
||||
Save(dir, path string) error
|
||||
}
|
||||
|
||||
69
internal/artifact/artifact.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package artifact
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
)
|
||||
|
||||
func New() *Artifact {
|
||||
return &Artifact{m: make(map[string][]byte)}
|
||||
}
|
||||
|
||||
// Artifact represents the fully rendered manifests build from the holos
|
||||
// rendering pipeline. Files are organized by keys representing paths relative
|
||||
// to the current working directory. Values represent the file content.
|
||||
type Artifact struct {
|
||||
mu sync.RWMutex
|
||||
m map[string][]byte
|
||||
}
|
||||
|
||||
// Set sets an artifact file with write locking. Set returns an error if the
|
||||
// artifact was previously set.
|
||||
func (a *Artifact) Set(path string, data []byte) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if _, ok := a.m[path]; ok {
|
||||
return errors.Format("%s already set", path)
|
||||
}
|
||||
a.m[path] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets the content of an artifact with read locking.
|
||||
func (a *Artifact) Get(path string) (data []byte, ok bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
data, ok = a.m[path]
|
||||
return
|
||||
}
|
||||
|
||||
// Save writes a file to the filesystem.
|
||||
func (a *Artifact) Save(dir, path string) error {
|
||||
fullPath := filepath.Join(dir, path)
|
||||
msg := fmt.Sprintf("could not save %s", fullPath)
|
||||
data, ok := a.Get(path)
|
||||
if !ok {
|
||||
return errors.Format("%s: missing %s", msg, path)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
if err := os.WriteFile(fullPath, data, 0666); err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Keys() []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
keys := make([]string, 0, len(a.m))
|
||||
for key := range a.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
@@ -15,10 +15,12 @@ import (
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"cuelang.org/go/cue/load"
|
||||
v1 "github.com/holos-run/holos/api/core/v1alpha3"
|
||||
"github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
core_v1alpha2 "github.com/holos-run/holos/api/core/v1alpha2"
|
||||
coreA3 "github.com/holos-run/holos/api/core/v1alpha3"
|
||||
metaA2 "github.com/holos-run/holos/api/meta/v1alpha2"
|
||||
"github.com/holos-run/holos/api/v1alpha1"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
@@ -26,10 +28,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
KubernetesObjects = v1.KubernetesObjectsKind
|
||||
KubernetesObjects = coreA3.KubernetesObjectsKind
|
||||
// Helm is the value of the kind field of holos build output indicating helm
|
||||
// values and helm command information.
|
||||
Helm = v1.HelmChartKind
|
||||
Helm = coreA3.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
|
||||
@@ -43,11 +45,7 @@ type Option func(*config)
|
||||
type config struct {
|
||||
args []string
|
||||
cluster string
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
cfg config
|
||||
ctx *cue.Context
|
||||
tags []string
|
||||
}
|
||||
|
||||
// BuildData represents the data necessary to produce a build plan. It is a
|
||||
@@ -59,8 +57,26 @@ type BuildData struct {
|
||||
Dir string
|
||||
}
|
||||
|
||||
func (bd *BuildData) TypeMeta() (tm holos.TypeMeta, err error) {
|
||||
jsonBytes, err := bd.Value.MarshalJSON()
|
||||
if err != nil {
|
||||
err = errors.Format("could not marshal json %s: %w", bd.Dir, err)
|
||||
return
|
||||
}
|
||||
err = json.NewDecoder(bytes.NewReader(jsonBytes)).Decode(&tm)
|
||||
if err != nil {
|
||||
err = errors.Format("could not decode type meta %s: %w", bd.Dir, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
cfg config
|
||||
ctx *cue.Context
|
||||
}
|
||||
|
||||
type buildPlanWrapper struct {
|
||||
buildPlan *v1.BuildPlan
|
||||
buildPlan *coreA3.BuildPlan
|
||||
}
|
||||
|
||||
func (b *buildPlanWrapper) validate() error {
|
||||
@@ -72,7 +88,7 @@ func (b *buildPlanWrapper) validate() error {
|
||||
return fmt.Errorf("invalid BuildPlan: is nil")
|
||||
}
|
||||
errs := make([]string, 0, 2)
|
||||
if bp.Kind != v1.BuildPlanKind {
|
||||
if bp.Kind != coreA3.BuildPlanKind {
|
||||
errs = append(errs, fmt.Sprintf("kind invalid: want: %s have: %s", v1alpha1.BuildPlanKind, bp.Kind))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
@@ -116,6 +132,11 @@ func Cluster(name string) Option {
|
||||
return func(cfg *config) { cfg.cluster = name }
|
||||
}
|
||||
|
||||
// Tags configures tags to pass to cue when building the instance.
|
||||
func Tags(tags []string) Option {
|
||||
return func(cfg *config) { cfg.tags = tags }
|
||||
}
|
||||
|
||||
// Cluster returns the cluster name of the component instance being built.
|
||||
func (b *Builder) Cluster() string {
|
||||
return b.cfg.cluster
|
||||
@@ -141,15 +162,19 @@ func (b *Builder) Unify(ctx context.Context, cfg *client.Config) (bd BuildData,
|
||||
return bd, errors.Wrap(fmt.Errorf("could not load platform model: %w", err))
|
||||
}
|
||||
|
||||
tags := make([]string, 0, len(b.cfg.tags)+2)
|
||||
tags = append(tags,
|
||||
"cluster="+cfg.Holos().ClusterName(),
|
||||
// TODO: Use instance.FillPath to fill the platform config.
|
||||
// Refer to https://pkg.go.dev/cuelang.org/go/cue#Value.FillPath
|
||||
"platform_config="+string(platformConfigData),
|
||||
)
|
||||
tags = append(tags, b.cfg.tags...)
|
||||
|
||||
cueConfig := load.Config{
|
||||
Dir: bd.ModuleRoot,
|
||||
ModuleRoot: bd.ModuleRoot,
|
||||
// TODO: Use instance.FillPath to fill the platform config.
|
||||
// Refer to https://pkg.go.dev/cuelang.org/go/cue#Value.FillPath
|
||||
Tags: []string{
|
||||
"cluster=" + cfg.Holos().ClusterName(),
|
||||
"platform_config=" + string(platformConfigData),
|
||||
},
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
// Make args relative to the module directory
|
||||
@@ -229,6 +254,8 @@ func loadUserData(ctx *cue.Context, moduleRoot string) (val cue.Value, err error
|
||||
// provided to the entrypoint through a json encoded string tag named
|
||||
// platform_config. The resulting cue.Value is unified with all user data files
|
||||
// at the path "#UserData".
|
||||
//
|
||||
// Deprecated: Use holos.Builder instead
|
||||
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")
|
||||
@@ -240,7 +267,7 @@ func (b *Builder) Run(ctx context.Context, cfg *client.Config) (results []*rende
|
||||
return b.build(ctx, bd)
|
||||
}
|
||||
|
||||
func (b Builder) build(ctx context.Context, bd BuildData) (results []*render.Result, err error) {
|
||||
func (b *Builder) build(ctx context.Context, bd BuildData) (results []*render.Result, err error) {
|
||||
log := logger.FromContext(ctx).With("dir", bd.InstancePath)
|
||||
value := bd.Value
|
||||
|
||||
@@ -279,7 +306,7 @@ func (b Builder) build(ctx context.Context, bd BuildData) (results []*render.Res
|
||||
|
||||
switch tm.Kind {
|
||||
case "BuildPlan":
|
||||
var bp v1.BuildPlan
|
||||
var bp coreA3.BuildPlan
|
||||
if err = decoder.Decode(&bp); err != nil {
|
||||
err = errors.Wrap(fmt.Errorf("could not decode BuildPlan %s: %w", bd.Dir, err))
|
||||
return
|
||||
@@ -295,7 +322,7 @@ func (b Builder) build(ctx context.Context, bd BuildData) (results []*render.Res
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1.BuildPlan, path holos.InstancePath) (results []*render.Result, err error) {
|
||||
func (b *Builder) buildPlan(ctx context.Context, buildPlan *coreA3.BuildPlan, path holos.InstancePath) (results []*render.Result, err error) {
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
bpw := buildPlanWrapper{buildPlan: buildPlan}
|
||||
@@ -381,3 +408,62 @@ func (b *Builder) findCueMod() (dir holos.PathCueMod, err error) {
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Platform builds a platform
|
||||
// TODO: Refactor, lift up into NewPlatform RunE.
|
||||
func (b *Builder) Platform(ctx context.Context, cfg *client.Config) (*core_v1alpha2.Platform, error) {
|
||||
log := logger.FromContext(ctx)
|
||||
log.DebugContext(ctx, "cue: building platform instance")
|
||||
bd, err := b.Unify(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
return b.runPlatform(ctx, bd)
|
||||
}
|
||||
|
||||
func (b *Builder) runPlatform(ctx context.Context, bd BuildData) (*core_v1alpha2.Platform, error) {
|
||||
path := holos.InstancePath(bd.Dir)
|
||||
log := logger.FromContext(ctx).With("dir", path)
|
||||
|
||||
value := bd.Value
|
||||
if err := bd.Value.Err(); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not load: %w", err))
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "cue: validating instance")
|
||||
if err := value.Validate(); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not validate: %w", err))
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "cue: decoding holos platform")
|
||||
jsonBytes, err := value.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", bd.Dir, err))
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
// Discriminate the type of build plan.
|
||||
tm := &metaA2.TypeMeta{}
|
||||
err = decoder.Decode(tm)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("invalid platform: %s: %w", bd.Dir, err))
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "cue: discriminated build kind: "+tm.GetKind(), "kind", tm.GetKind(), "apiVersion", tm.GetAPIVersion())
|
||||
decoder = json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
var pf core_v1alpha2.Platform
|
||||
switch tm.GetKind() {
|
||||
case "Platform":
|
||||
if err = decoder.Decode(&pf); err != nil {
|
||||
err = errors.Wrap(fmt.Errorf("could not decode platform %s: %w", bd.Dir, err))
|
||||
return nil, err
|
||||
}
|
||||
return &pf, nil
|
||||
default:
|
||||
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.GetKind()))
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
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) (*core.Platform, error) {
|
||||
log := logger.FromContext(ctx)
|
||||
log.DebugContext(ctx, "cue: building platform instance")
|
||||
bd, err := b.Unify(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
return b.runPlatform(ctx, bd)
|
||||
}
|
||||
|
||||
func (b Builder) runPlatform(ctx context.Context, bd BuildData) (*core.Platform, error) {
|
||||
path := holos.InstancePath(bd.Dir)
|
||||
log := logger.FromContext(ctx).With("dir", path)
|
||||
|
||||
value := bd.Value
|
||||
if err := bd.Value.Err(); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not load: %w", err))
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "cue: validating instance")
|
||||
if err := value.Validate(); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not validate: %w", err))
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "cue: decoding holos platform")
|
||||
jsonBytes, err := value.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", bd.Dir, err))
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
// Discriminate the type of build plan.
|
||||
tm := &meta.TypeMeta{}
|
||||
err = decoder.Decode(tm)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("invalid platform: %s: %w", bd.Dir, err))
|
||||
}
|
||||
|
||||
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 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", bd.Dir, err))
|
||||
return nil, err
|
||||
}
|
||||
return &pf, nil
|
||||
default:
|
||||
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.GetKind()))
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
543
internal/builder/v1alpha4/builder.go
Normal file
@@ -0,0 +1,543 @@
|
||||
package v1alpha4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
h "github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/api/core/v1alpha4"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
"github.com/holos-run/holos/internal/util"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Platform represents a platform builder.
|
||||
type Platform struct {
|
||||
Platform v1alpha4.Platform
|
||||
Concurrency int
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
// Build builds a Platform by concurrently building a BuildPlan for each
|
||||
// platform component. No artifact files are written directly, only indirectly
|
||||
// by rendering each component.
|
||||
func (p *Platform) Build(ctx context.Context, _ h.ArtifactMap) error {
|
||||
parentStart := time.Now()
|
||||
components := p.Platform.Spec.Components
|
||||
total := len(components)
|
||||
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(p.Concurrency + 1)
|
||||
// Spawn a producer because g.Go() blocks when the group limit is reached.
|
||||
g.Go(func() error {
|
||||
for idx := range components {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
// Capture idx to avoid issues with closure. Fixed in Go 1.22.
|
||||
idx := idx
|
||||
component := &components[idx]
|
||||
// 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(
|
||||
"name", component.Name,
|
||||
"path", component.Component,
|
||||
"cluster", component.Cluster,
|
||||
"environment", component.Environment,
|
||||
"num", idx+1,
|
||||
"total", total,
|
||||
)
|
||||
log.DebugContext(ctx, "render component")
|
||||
|
||||
tags := make([]string, 0, 3+len(component.Tags))
|
||||
tags = append(tags, "name="+component.Name)
|
||||
tags = append(tags, "component="+component.Component)
|
||||
tags = append(tags, "environment="+component.Environment)
|
||||
// Tags are unified, cue handles conflicts. We don't bother.
|
||||
tags = append(tags, component.Tags...)
|
||||
|
||||
// Execute a sub-process to limit CUE memory usage.
|
||||
args := []string{
|
||||
"render",
|
||||
"component",
|
||||
"--cluster-name", component.Cluster,
|
||||
"--tags", strings.Join(tags, ","),
|
||||
component.Component,
|
||||
}
|
||||
result, err := util.RunCmd(ctx, "holos", args...)
|
||||
// I've lost an hour+ digging into why I couldn't see log output
|
||||
// from sub-processes. Make sure to surface at least stderr from
|
||||
// sub-processes.
|
||||
_, _ = io.Copy(p.Stderr, result.Stderr)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("could not render component: %w", err))
|
||||
}
|
||||
|
||||
duration := time.Since(start)
|
||||
msg := fmt.Sprintf(
|
||||
"rendered %s for cluster %s in %s",
|
||||
component.Name,
|
||||
component.Cluster,
|
||||
duration,
|
||||
)
|
||||
log.InfoContext(ctx, msg, "duration", duration)
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Wait for completion and return the first error (if any)
|
||||
if err := g.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
duration := time.Since(parentStart)
|
||||
msg := fmt.Sprintf("rendered platform in %s", duration)
|
||||
logger.FromContext(ctx).InfoContext(ctx, msg, "duration", duration, "version", p.Platform.APIVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildPlan represents a component builder.
|
||||
type BuildPlan struct {
|
||||
BuildPlan v1alpha4.BuildPlan
|
||||
Concurrency int
|
||||
Stderr io.Writer
|
||||
// WriteTo --write-to=deploy flag
|
||||
WriteTo string
|
||||
// Path represents the path to the component
|
||||
Path h.InstancePath
|
||||
}
|
||||
|
||||
// Build builds a BuildPlan into Artifact files.
|
||||
func (b *BuildPlan) Build(ctx context.Context, am h.ArtifactMap) error {
|
||||
name := b.BuildPlan.Metadata.Name
|
||||
component := b.BuildPlan.Spec.Component
|
||||
log := logger.FromContext(ctx).With("name", name, "component", component)
|
||||
msg := fmt.Sprintf("could not build %s", name)
|
||||
if b.BuildPlan.Spec.Disabled {
|
||||
log.WarnContext(ctx, fmt.Sprintf("%s: disabled", msg))
|
||||
return nil
|
||||
}
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
// One more for the producer
|
||||
g.SetLimit(b.Concurrency + 1)
|
||||
|
||||
// Producer.
|
||||
g.Go(func() error {
|
||||
for _, a := range b.BuildPlan.Spec.Artifacts {
|
||||
msg := fmt.Sprintf("%s artifact %s", msg, a.Artifact)
|
||||
log := log.With("artifact", a.Artifact)
|
||||
if a.Skip {
|
||||
log.WarnContext(ctx, fmt.Sprintf("%s: skipped field is true", msg))
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
// https://golang.org/doc/faq#closures_and_goroutines
|
||||
a := a
|
||||
// Worker. Blocks if limit has been reached.
|
||||
g.Go(func() error {
|
||||
for _, gen := range a.Generators {
|
||||
switch gen.Kind {
|
||||
case "Resources":
|
||||
if err := b.resources(log, gen, am); err != nil {
|
||||
return errors.Format("could not generate resources: %w", err)
|
||||
}
|
||||
case "Helm":
|
||||
if err := b.helm(ctx, log, gen, am); err != nil {
|
||||
return errors.Format("could not generate helm: %w", err)
|
||||
}
|
||||
case "File":
|
||||
if err := b.file(log, gen, am); err != nil {
|
||||
return errors.Format("could not generate file: %w", err)
|
||||
}
|
||||
default:
|
||||
return errors.Format("%s: unsupported kind %s", msg, gen.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range a.Transformers {
|
||||
switch t.Kind {
|
||||
case "Kustomize":
|
||||
if err := b.kustomize(ctx, log, t, am); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
case "Join":
|
||||
s := make([][]byte, 0, len(t.Inputs))
|
||||
for _, input := range t.Inputs {
|
||||
if data, ok := am.Get(string(input)); ok {
|
||||
s = append(s, data)
|
||||
} else {
|
||||
return errors.Format("%s: missing %s", msg, input)
|
||||
}
|
||||
}
|
||||
data := bytes.Join(s, []byte(t.Join.Separator))
|
||||
if err := am.Set(string(t.Output), data); err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
log.Debug("set artifact: " + string(t.Output))
|
||||
default:
|
||||
return errors.Format("%s: unsupported kind %s", msg, t.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
// Write the final artifact
|
||||
if err := am.Save(b.WriteTo, string(a.Artifact)); err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
log.DebugContext(ctx, "wrote "+filepath.Join(b.WriteTo, string(a.Artifact)))
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Wait for completion and return the first error (if any)
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (b *BuildPlan) file(
|
||||
log *slog.Logger,
|
||||
g v1alpha4.Generator,
|
||||
am h.ArtifactMap,
|
||||
) error {
|
||||
data, err := os.ReadFile(filepath.Join(string(b.Path), string(g.File.Source)))
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
if err := am.Set(string(g.Output), data); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.Debug("set artifact: " + string(g.Output))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BuildPlan) helm(
|
||||
ctx context.Context,
|
||||
log *slog.Logger,
|
||||
g v1alpha4.Generator,
|
||||
am h.ArtifactMap,
|
||||
) error {
|
||||
chartName := g.Helm.Chart.Name
|
||||
log = log.With("chart", chartName)
|
||||
// Unnecessary? cargo cult copied from internal/cli/render/render.go
|
||||
if chartName == "" {
|
||||
return errors.New("missing chart name")
|
||||
}
|
||||
|
||||
// Cache the chart by version to pull new versions. (#273)
|
||||
cacheDir := filepath.Join(string(b.Path), "vendor", g.Helm.Chart.Version)
|
||||
cachePath := filepath.Join(cacheDir, filepath.Base(chartName))
|
||||
|
||||
if _, err := os.Stat(cachePath); os.IsNotExist(err) {
|
||||
timeout, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
||||
defer cancel()
|
||||
err := onceWithLock(log, timeout, cachePath, func() error {
|
||||
return b.cacheChart(ctx, log, cacheDir, g.Helm.Chart)
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Format("could not cache chart: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Write values file
|
||||
tempDir, err := os.MkdirTemp("", "holos.helm")
|
||||
if err != nil {
|
||||
return errors.Format("could not make temp dir: %w", err)
|
||||
}
|
||||
defer util.Remove(ctx, tempDir)
|
||||
|
||||
data, err := yaml.Marshal(g.Helm.Values)
|
||||
if err != nil {
|
||||
return errors.Format("could not marshal values: %w", err)
|
||||
}
|
||||
|
||||
valuesPath := filepath.Join(tempDir, "values.yaml")
|
||||
if err := os.WriteFile(valuesPath, data, 0666); err != nil {
|
||||
return errors.Wrap(fmt.Errorf("could not write values: %w", err))
|
||||
}
|
||||
log.DebugContext(ctx, "wrote"+valuesPath)
|
||||
|
||||
// Run charts
|
||||
args := []string{"template"}
|
||||
if g.Helm.EnableHooks {
|
||||
args = append(args, "--hooks")
|
||||
} else {
|
||||
args = append(args, "--no-hooks")
|
||||
}
|
||||
args = append(args,
|
||||
"--include-crds",
|
||||
"--values", valuesPath,
|
||||
"--namespace", g.Helm.Namespace,
|
||||
"--kubeconfig", "/dev/null",
|
||||
"--version", g.Helm.Chart.Version,
|
||||
g.Helm.Chart.Release,
|
||||
cachePath,
|
||||
)
|
||||
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.Format("could not run helm template: %w", err)
|
||||
}
|
||||
|
||||
// Set the artifact
|
||||
if err := am.Set(string(g.Output), helmOut.Stdout.Bytes()); err != nil {
|
||||
return errors.Format("could not store helm output: %w", err)
|
||||
}
|
||||
log.Debug("set artifact: " + string(g.Output))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BuildPlan) resources(
|
||||
log *slog.Logger,
|
||||
g v1alpha4.Generator,
|
||||
am h.ArtifactMap,
|
||||
) error {
|
||||
var size int
|
||||
for _, m := range g.Resources {
|
||||
size += len(m)
|
||||
}
|
||||
list := make([]v1alpha4.Resource, 0, size)
|
||||
|
||||
for _, m := range g.Resources {
|
||||
for _, r := range m {
|
||||
list = append(list, r)
|
||||
}
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("could not generate %s for %s", g.Output, b.BuildPlan.Metadata.Name)
|
||||
|
||||
buf, err := marshal(list)
|
||||
if err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
|
||||
if err := am.Set(string(g.Output), buf.Bytes()); err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
|
||||
log.Debug("set artifact " + string(g.Output))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BuildPlan) kustomize(
|
||||
ctx context.Context,
|
||||
log *slog.Logger,
|
||||
t v1alpha4.Transformer,
|
||||
am h.ArtifactMap,
|
||||
) error {
|
||||
tempDir, err := os.MkdirTemp("", "holos.kustomize")
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
defer util.Remove(ctx, tempDir)
|
||||
msg := fmt.Sprintf("could not transform %s for %s", t.Output, b.BuildPlan.Metadata.Name)
|
||||
|
||||
// Write the kustomization
|
||||
data, err := yaml.Marshal(t.Kustomize.Kustomization)
|
||||
if err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
path := filepath.Join(tempDir, "kustomization.yaml")
|
||||
if err := os.WriteFile(path, data, 0666); err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
log.DebugContext(ctx, "wrote "+path)
|
||||
|
||||
// Write the inputs
|
||||
for _, input := range t.Inputs {
|
||||
path := string(input)
|
||||
if err := am.Save(tempDir, path); err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
log.DebugContext(ctx, "wrote "+filepath.Join(tempDir, path))
|
||||
}
|
||||
|
||||
// Execute kustomize
|
||||
r, err := util.RunCmd(ctx, "kubectl", "kustomize", tempDir)
|
||||
if err != nil {
|
||||
err = errors.Format("%s: could not run kustomize: %w", msg, err)
|
||||
if s := strings.ReplaceAll(r.Stderr.String(), "\n", "\n\t"); s != "" {
|
||||
err = errors.Format("%w\n\t%s", err, s)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the artifact
|
||||
if err := am.Set(string(t.Output), r.Stdout.Bytes()); err != nil {
|
||||
return errors.Format("%s: %w", msg, err)
|
||||
}
|
||||
log.Debug("set artifact " + string(t.Output))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshal(list []v1alpha4.Resource) (buf bytes.Buffer, err error) {
|
||||
encoder := yaml.NewEncoder(&buf)
|
||||
defer encoder.Close()
|
||||
for _, item := range list {
|
||||
if err = encoder.Encode(item); err != nil {
|
||||
err = errors.Wrap(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cacheChart stores a cached copy of Chart in the chart subdirectory of path.
|
||||
//
|
||||
// We assume the only method responsible for writing to chartDir is cacheChart
|
||||
// itself. cacheChart runs concurrently when rendering a platform.
|
||||
//
|
||||
// We rely 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
|
||||
// expected when this function is called concurrently.
|
||||
//
|
||||
// TODO(jeff): Break the dependency on v1alpha4, make it work across versions as
|
||||
// a utility function.
|
||||
func (b *BuildPlan) cacheChart(
|
||||
ctx context.Context,
|
||||
log *slog.Logger,
|
||||
cacheDir string,
|
||||
chart v1alpha4.Chart,
|
||||
) error {
|
||||
// Add repositories
|
||||
repo := chart.Repository
|
||||
if repo.URL == "" {
|
||||
// repo update not needed for oci charts so this is debug instead of warn.
|
||||
log.DebugContext(ctx, "skipped helm repo add and update: repo url is empty")
|
||||
} else {
|
||||
if r, err := util.RunCmd(ctx, "helm", "repo", "add", repo.Name, repo.URL); err != nil {
|
||||
_, _ = io.Copy(b.Stderr, r.Stderr)
|
||||
return errors.Format("could not run helm repo add: %w", err)
|
||||
}
|
||||
if r, err := util.RunCmd(ctx, "helm", "repo", "update", repo.Name); err != nil {
|
||||
_, _ = io.Copy(b.Stderr, r.Stderr)
|
||||
return errors.Format("could not run helm repo update: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cacheTemp, err := os.MkdirTemp(cacheDir, chart.Name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
defer util.Remove(ctx, cacheTemp)
|
||||
|
||||
cn := chart.Name
|
||||
if chart.Repository.Name != "" {
|
||||
cn = fmt.Sprintf("%s/%s", chart.Repository.Name, chart.Name)
|
||||
}
|
||||
helmOut, err := util.RunCmd(ctx, "helm", "pull", "--destination", cacheTemp, "--untar=true", "--version", chart.Version, cn)
|
||||
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)
|
||||
|
||||
items, err := os.ReadDir(cacheTemp)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("could not read directory: %w", err))
|
||||
}
|
||||
if len(items) != 1 {
|
||||
return errors.Format("want: exactly one item, have: %+v", items)
|
||||
}
|
||||
item := items[0]
|
||||
|
||||
src := filepath.Join(cacheTemp, item.Name())
|
||||
dst := filepath.Join(cacheDir, chart.Name)
|
||||
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", dst)
|
||||
} else {
|
||||
return errors.Wrap(fmt.Errorf("could not rename: %w", err))
|
||||
}
|
||||
} else {
|
||||
log.DebugContext(ctx, fmt.Sprintf("renamed %s to %s", src, dst), "src", src, "dst", dst)
|
||||
}
|
||||
|
||||
log.InfoContext(ctx,
|
||||
fmt.Sprintf("cached %s %s", chart.Name, chart.Version),
|
||||
"chart", chart.Name,
|
||||
"chart_version", chart.Version,
|
||||
"path", dst,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// onceWithLock obtains a filesystem lock with mkdir, then executes fn. If the
|
||||
// lock is already locked, onceWithLock waits for it to be released then returns
|
||||
// without calling fn.
|
||||
func onceWithLock(log *slog.Logger, ctx context.Context, path string, fn func() error) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// Obtain a lock with a timeout.
|
||||
lockDir := path + ".lock"
|
||||
log = log.With("lock", lockDir)
|
||||
|
||||
err := os.Mkdir(lockDir, 0777)
|
||||
if err == nil {
|
||||
log.DebugContext(ctx, fmt.Sprintf("acquired %s", lockDir))
|
||||
defer os.RemoveAll(lockDir)
|
||||
if err := fn(); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.DebugContext(ctx, fmt.Sprintf("released %s", lockDir))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait until the lock is released then return.
|
||||
if os.IsExist(err) {
|
||||
log.DebugContext(ctx, fmt.Sprintf("blocked %s", lockDir))
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.Wrap(ctx.Err())
|
||||
default:
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if _, err := os.Stat(lockDir); os.IsNotExist(err) {
|
||||
log.DebugContext(ctx, fmt.Sprintf("unblocked %s", lockDir))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unexpected error
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
@@ -20,6 +20,7 @@ func makeBuildRunFunc(cfg *client.Config) command.RunFunc {
|
||||
ctx := cmd.Root().Context()
|
||||
logger.FromContext(ctx).DebugContext(ctx, "RunE", "args", args)
|
||||
build := builder.New(builder.Entrypoints(args), builder.Cluster(cfg.Holos().ClusterName()))
|
||||
//nolint:staticcheck
|
||||
results, err := build.Run(ctx, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
h "github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/internal/artifact"
|
||||
"github.com/holos-run/holos/internal/builder"
|
||||
"github.com/holos-run/holos/internal/builder/v1alpha4"
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
@@ -26,8 +33,8 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
|
||||
// New returns the component subcommand for the render command
|
||||
func NewComponent(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("component DIRECTORY [DIRECTORY...]")
|
||||
cmd.Args = cobra.MinimumNArgs(1)
|
||||
cmd := command.New("component DIRECTORY")
|
||||
cmd.Args = cobra.ExactArgs(1)
|
||||
cmd.Short = "render specific components"
|
||||
cmd.Example = " holos render component --cluster-name=aws2 ./components/monitoring/kube-prometheus-stack"
|
||||
cmd.Flags().AddGoFlagSet(cfg.WriteFlagSet())
|
||||
@@ -38,46 +45,102 @@ func NewComponent(cfg *holos.Config) *cobra.Command {
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
|
||||
flagSet := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
|
||||
tagMap := make(tags)
|
||||
flagSet.Var(&tagMap, "tags", "cue tags as comma separated key=value pairs")
|
||||
var concurrency int
|
||||
flagSet.IntVar(&concurrency, "concurrency", min(runtime.NumCPU(), 8), "number of concurrent build steps")
|
||||
cmd.Flags().AddGoFlagSet(flagSet)
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
build := builder.New(builder.Entrypoints(args), builder.Cluster(cfg.ClusterName()))
|
||||
build := builder.New(
|
||||
builder.Entrypoints(args),
|
||||
builder.Cluster(cfg.ClusterName()),
|
||||
builder.Tags(tagMap.Tags()),
|
||||
)
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
results, err := build.Run(ctx, config)
|
||||
log.DebugContext(ctx, "cue: building component instance")
|
||||
bd, err := build.Unify(ctx, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
// TODO: Avoid accidental over-writes if two or more holos component
|
||||
// instances result in the same file path. Write files into a blank
|
||||
// temporary directory, error if a file exists, then move the directory into
|
||||
// place.
|
||||
var result Result
|
||||
for _, result = range results {
|
||||
log := logger.FromContext(ctx).With(
|
||||
"cluster", cfg.ClusterName(),
|
||||
"name", result.Name(),
|
||||
)
|
||||
if result.Continue() {
|
||||
continue
|
||||
|
||||
tm, err := bd.TypeMeta()
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
if tm.Kind != "BuildPlan" {
|
||||
return errors.Format("invalid kind: want: BuildPlan have: %s", tm.Kind)
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "discriminated "+tm.APIVersion+" "+tm.Kind)
|
||||
|
||||
jsonBytes, err := bd.Value.MarshalJSON()
|
||||
if err != nil {
|
||||
return errors.Format("could not marshal json %s: %w", bd.Dir, err)
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
art := artifact.New()
|
||||
|
||||
switch version := tm.APIVersion; version {
|
||||
case "v1alpha4":
|
||||
builder := v1alpha4.BuildPlan{
|
||||
WriteTo: cfg.WriteTo(),
|
||||
Concurrency: concurrency,
|
||||
Stderr: cmd.ErrOrStderr(),
|
||||
Path: h.InstancePath(args[0]),
|
||||
}
|
||||
// DeployFiles from the BuildPlan
|
||||
if err := result.WriteDeployFiles(ctx, cfg.WriteTo()); err != nil {
|
||||
if err := decoder.Decode(&builder.BuildPlan); err != nil {
|
||||
return errors.Format("could not decode build plan %s: %w", bd.Dir, err)
|
||||
}
|
||||
return render.Component(ctx, &builder, art)
|
||||
// Legacy method.
|
||||
case "v1alpha3", "v1alpha2", "v1alpha1":
|
||||
//nolint:staticcheck
|
||||
results, err := build.Run(ctx, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// API Objects
|
||||
if result.SkipWriteAccumulatedOutput() {
|
||||
log.DebugContext(ctx, "skipped writing k8s objects for "+result.Name())
|
||||
} else {
|
||||
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
|
||||
if err := result.Save(ctx, path, result.AccumulatedOutput()); err != nil {
|
||||
// TODO: Avoid accidental over-writes if two or more holos component
|
||||
// instances result in the same file path. Write files into a blank
|
||||
// temporary directory, error if a file exists, then move the directory into
|
||||
// place.
|
||||
var result Result
|
||||
for _, result = range results {
|
||||
log := logger.FromContext(ctx).With(
|
||||
"cluster", cfg.ClusterName(),
|
||||
"name", result.Name(),
|
||||
)
|
||||
if result.Continue() {
|
||||
continue
|
||||
}
|
||||
// DeployFiles from the BuildPlan
|
||||
if err := result.WriteDeployFiles(ctx, cfg.WriteTo()); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// API Objects
|
||||
if result.SkipWriteAccumulatedOutput() {
|
||||
log.DebugContext(ctx, "skipped writing k8s objects for "+result.Name())
|
||||
} else {
|
||||
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
|
||||
if err := result.Save(ctx, path, result.AccumulatedOutput()); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
log.InfoContext(ctx, "rendered "+result.Name(), "status", "ok", "action", "rendered")
|
||||
}
|
||||
|
||||
log.InfoContext(ctx, "rendered "+result.Name(), "status", "ok", "action", "rendered")
|
||||
default:
|
||||
return errors.Format("component version not supported: %s", version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return cmd
|
||||
@@ -99,18 +162,87 @@ func NewPlatform(cfg *holos.Config) *cobra.Command {
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
build := builder.New(builder.Entrypoints(args))
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
platform, err := build.Platform(ctx, config)
|
||||
log.DebugContext(ctx, "cue: building platform instance")
|
||||
bd, err := build.Unify(ctx, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
return render.Platform(ctx, concurrency, platform, cmd.ErrOrStderr())
|
||||
tm, err := bd.TypeMeta()
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
if tm.Kind != "Platform" {
|
||||
return errors.Format("invalid kind: want: Platform have: %s", tm.Kind)
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "discriminated "+tm.APIVersion+" "+tm.Kind)
|
||||
|
||||
jsonBytes, err := bd.Value.MarshalJSON()
|
||||
if err != nil {
|
||||
return errors.Format("could not marshal json %s: %w", bd.Dir, err)
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
switch version := tm.APIVersion; version {
|
||||
case "v1alpha4":
|
||||
builder := v1alpha4.Platform{
|
||||
Concurrency: concurrency,
|
||||
Stderr: cmd.ErrOrStderr(),
|
||||
}
|
||||
if err := decoder.Decode(&builder.Platform); err != nil {
|
||||
return errors.Format("could not decode platform %s: %w", bd.Dir, err)
|
||||
}
|
||||
return render.Platform(ctx, &builder)
|
||||
|
||||
// Legacy versions prior to the render.Builder interface.
|
||||
case "v1alpha3", "v1alpha2", "v1alpha1":
|
||||
platform, err := build.Platform(ctx, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
//nolint:staticcheck
|
||||
return render.LegacyPlatform(ctx, concurrency, platform, cmd.ErrOrStderr())
|
||||
|
||||
default:
|
||||
return errors.Format("platform version not supported: %s", version)
|
||||
}
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// tags represents a map of key values for CUE tags for flag parsing.
|
||||
type tags map[string]string
|
||||
|
||||
func (t tags) Tags() []string {
|
||||
parts := make([]string, 0, len(t))
|
||||
for k, v := range t {
|
||||
parts = append(parts, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func (t tags) String() string {
|
||||
return strings.Join(t.Tags(), ",")
|
||||
}
|
||||
|
||||
func (t tags) Set(value string) error {
|
||||
for _, item := range strings.Split(value, ",") {
|
||||
parts := strings.SplitN(item, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return errors.Format("invalid format, must be tag=value")
|
||||
}
|
||||
t[parts[0]] = parts[1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: use render.Artifact instead.
|
||||
type Result interface {
|
||||
Continue() bool
|
||||
Name() string
|
||||
|
||||
@@ -13,6 +13,16 @@ import (
|
||||
// ErrUnsupported is errors.ErrUnsupported
|
||||
var ErrUnsupported = errors.ErrUnsupported
|
||||
|
||||
func NotImplemented() error {
|
||||
return wrap(New("not implemented"), 2)
|
||||
}
|
||||
|
||||
// Format calls fmt.Format(format, a...) and wraps the error with the caller
|
||||
// source location.
|
||||
func Format(format string, a ...any) error {
|
||||
return wrap(fmt.Errorf(format, a...), 2)
|
||||
}
|
||||
|
||||
// As calls errors.As
|
||||
func As(err error, target any) bool {
|
||||
return errors.As(err, target)
|
||||
@@ -67,11 +77,7 @@ func (e *ErrorAt) Error() string {
|
||||
return e.Source.Loc() + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// Wrap wraps err in a ErrorAt or returns err if err is nil, already a
|
||||
// ErrorAt, or caller info is not available.
|
||||
//
|
||||
// XXX: Refactor to Err(error, ...slog.Attr). Often want to add attributes for the top level logger.
|
||||
func Wrap(err error) error {
|
||||
func wrap(err error, skip int) error {
|
||||
// Nothing to do
|
||||
if err == nil {
|
||||
return nil
|
||||
@@ -84,7 +90,7 @@ func Wrap(err error) error {
|
||||
}
|
||||
|
||||
// Try to wrap err with caller info
|
||||
if _, file, line, ok := runtime.Caller(1); ok {
|
||||
if _, file, line, ok := runtime.Caller(skip); ok {
|
||||
return &ErrorAt{
|
||||
Err: err,
|
||||
Source: Source{
|
||||
@@ -97,6 +103,15 @@ func Wrap(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wrap wraps err in a ErrorAt or returns err if err is nil, already a
|
||||
// ErrorAt, or caller info is not available.
|
||||
//
|
||||
// XXX: Refactor to Err(error, ...slog.Attr). Often want to add attributes for
|
||||
// the top level logger.
|
||||
func Wrap(err error) error {
|
||||
return wrap(err, 2)
|
||||
}
|
||||
|
||||
// Log logs err with Source location if Err is a ErrorAt
|
||||
func Log(log *slog.Logger, ctx context.Context, level slog.Level, err error, msg string, args ...any) {
|
||||
var errAt *ErrorAt
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubectl kustomize build plan.
|
||||
(#Kustomize & {Name: "{{ .Name }}"}).Output
|
||||
(#Kustomize & {Name: "{{ .Name }}"}).BuildPlan
|
||||
|
||||
@@ -18,4 +18,4 @@ let Objects = {
|
||||
}
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
@@ -8,4 +8,4 @@ let Objects = {
|
||||
}
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
@@ -17,4 +17,4 @@ let Chart = {
|
||||
}
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
(#Helm & Chart).BuildPlan
|
||||
|
||||
@@ -12,4 +12,4 @@ let Chart = {
|
||||
}
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
(#Helm & Chart).BuildPlan
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package holos
|
||||
|
||||
// Manage the Component on every Cluster in the Platform
|
||||
for Fleet in #Fleets {
|
||||
for Cluster in Fleet.clusters {
|
||||
#Platform: Components: "\(Cluster.name)/app-projects": {
|
||||
path: "projects/platform/components/app-projects"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package holos
|
||||
|
||||
import ap "argoproj.io/appproject/v1alpha1"
|
||||
|
||||
// Registration point for AppProjects
|
||||
#AppProjects: [Name=string]: ap.#AppProject & {
|
||||
metadata: name: Name
|
||||
metadata: namespace: #ArgoCD.Namespace
|
||||
spec: description: string | *"Holos managed AppProject"
|
||||
spec: clusterResourceWhitelist: [{group: "*", kind: "*"}]
|
||||
spec: destinations: [{namespace: "*", server: "*"}]
|
||||
spec: sourceRepos: ["*"]
|
||||
}
|
||||
|
||||
// Define at least the platform project. Other components can register projects
|
||||
// the same way from the root of the configuration.
|
||||
#AppProjects: platform: _
|
||||
@@ -0,0 +1,9 @@
|
||||
package holos
|
||||
|
||||
let Objects = {
|
||||
Name: "app-projects"
|
||||
Resources: AppProject: #AppProjects
|
||||
}
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "app-projects",
|
||||
"short": "#AppProjects registration point",
|
||||
"long": "Manage ArgoCD AppProject resources centrally."
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package holos
|
||||
|
||||
#ArgoConfig: {
|
||||
Enabled: true
|
||||
RepoURL: "https://github.com/holos-run/holos-manage-a-project-guide"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package holos
|
||||
|
||||
#ArgoConfig: AppProject: #AppProjects.platform.metadata.name
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "argocd-config-manage-a-project-guide",
|
||||
"short": "generate applications for the manage-a-project guide",
|
||||
"long": "https://github.com/holos-run/holos-manage-a-project-guide"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package holos
|
||||
|
||||
// Manage the Component on every Cluster in the Platform
|
||||
for Fleet in #Fleets {
|
||||
for Cluster in Fleet.clusters {
|
||||
#Platform: Components: "\(Cluster.name)/argocd-crds": {
|
||||
path: "projects/platform/components/argocd/crds"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
#Platform: Components: "\(Cluster.name)/argocd": {
|
||||
path: "projects/platform/components/argocd/argocd"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package holos
|
||||
|
||||
// #ArgoCD represents platform wide configuration
|
||||
#ArgoCD: {
|
||||
Version: "2.12.3"
|
||||
Namespace: "argocd"
|
||||
}
|
||||
|
||||
// Register namespaces
|
||||
#Namespaces: (#ArgoCD.Namespace): _
|
||||
|
||||
// Register the HTTPRoute to the backend Service
|
||||
#HTTPRoutes: argocd: _backendRefs: "argocd-server": namespace: #ArgoCD.Namespace
|
||||
@@ -0,0 +1,60 @@
|
||||
package holos
|
||||
|
||||
import "strings"
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).BuildPlan
|
||||
|
||||
let Chart = {
|
||||
Name: "argocd"
|
||||
Namespace: #ArgoCD.Namespace
|
||||
Version: "7.5.2"
|
||||
|
||||
Repo: name: "argocd"
|
||||
Repo: url: "https://argoproj.github.io/argo-helm"
|
||||
|
||||
Chart: chart: name: "argo-cd"
|
||||
Chart: chart: release: Name
|
||||
// Upstream uses a Kubernetes Job to create the argocd-redis Secret. Enable
|
||||
// hooks to enable the Job.
|
||||
Chart: enableHooks: true
|
||||
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
// Grant the Gateway namespace the ability to refer to the backend service
|
||||
// from HTTPRoute resources.
|
||||
Resources: ReferenceGrant: (#Istio.Gateway.Namespace): #ReferenceGrant
|
||||
|
||||
EnableKustomizePostProcessor: true
|
||||
// Force all resources into the component namespace, some resources in the
|
||||
// helm chart may not specify the namespace so they may get mis-applied
|
||||
// depending on the kubectl (client-go) context.
|
||||
KustomizeFiles: "kustomization.yaml": namespace: Namespace
|
||||
|
||||
Values: #Values & {
|
||||
kubeVersionOverride: "1.29.0"
|
||||
// handled in the argo-crds component
|
||||
crds: install: false
|
||||
// Configure the same fqdn the HTTPRoute is configured with.
|
||||
global: domain: #HTTPRoutes.argocd.spec.hostnames[0]
|
||||
dex: enabled: false
|
||||
// the platform handles mutual tls to the backend
|
||||
configs: params: "server.insecure": true
|
||||
|
||||
configs: cm: {
|
||||
"admin.enabled": false
|
||||
"oidc.config": "{}"
|
||||
"users.anonymous.enabled": "true"
|
||||
}
|
||||
|
||||
// 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",
|
||||
]
|
||||
|
||||
configs: rbac: "policy.csv": strings.Join(Policy, "\n")
|
||||
configs: rbac: "policy.default": "role:admin"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package holos
|
||||
|
||||
import (
|
||||
"encoding/yaml"
|
||||
ks "sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
(#Kubernetes & {Name: "argocd-crds"}).BuildPlan
|
||||
|
||||
// Holos stages BuildPlan resources as an intermediate step of the rendering
|
||||
// pipeline. The purpose is to provide the resources to kustomize for
|
||||
// post-processing.
|
||||
let BuildPlanResources = "build-plan-resources.yaml"
|
||||
|
||||
let Kustomization = ks.#Kustomization & {
|
||||
apiVersion: "kustomize.config.k8s.io/v1beta1"
|
||||
kind: "Kustomization"
|
||||
resources: [
|
||||
// Kustomize the intermediate build plan resources.
|
||||
BuildPlanResources,
|
||||
// Mix-in external resources.
|
||||
"https://raw.githubusercontent.com/argoproj/argo-cd/v\(#ArgoCD.Version)/manifests/crds/application-crd.yaml",
|
||||
"https://raw.githubusercontent.com/argoproj/argo-cd/v\(#ArgoCD.Version)/manifests/crds/applicationset-crd.yaml",
|
||||
"https://raw.githubusercontent.com/argoproj/argo-cd/v\(#ArgoCD.Version)/manifests/crds/appproject-crd.yaml",
|
||||
// This method also works, but takes about 5 seconds longer each build.
|
||||
// "https://github.com/argoproj/argo-cd//manifests/crds/?ref=v\(#ArgoCD.Version)",
|
||||
]
|
||||
}
|
||||
|
||||
// Generate a kustomization.yaml directly from CUE so we can provide the correct
|
||||
// version.
|
||||
spec: components: kubernetesObjectsList: [{
|
||||
// intermediate build plan resources to kustomize. Necessary to activate the
|
||||
// kustomization post-rendering step in holos.
|
||||
kustomize: resourcesFile: BuildPlanResources
|
||||
kustomize: kustomizeFiles: "kustomization.yaml": yaml.Marshal(Kustomization)
|
||||
}]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "argocd",
|
||||
"short": "declaritive gitops for kubernetes",
|
||||
"long": "Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes."
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package holos
|
||||
|
||||
// Manage on workload clusters only
|
||||
for Cluster in #Fleets.workload.clusters {
|
||||
// Owned by the security team
|
||||
#Platform: Components: "\(Cluster.name)/bank-secrets": {
|
||||
path: "projects/bank-of-holos/security/components/bank-secrets"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
|
||||
// Owned by the frontend team
|
||||
#Platform: Components: "\(Cluster.name)/bank-frontend": {
|
||||
path: "projects/bank-of-holos/frontend/components/bank-frontend"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
|
||||
// Owned by the backend team
|
||||
#Platform: Components: "\(Cluster.name)/bank-backend-config": {
|
||||
path: "projects/bank-of-holos/backend/components/bank-backend-config"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
#Platform: Components: "\(Cluster.name)/bank-accounts-db": {
|
||||
path: "projects/bank-of-holos/backend/components/bank-accounts-db"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
#Platform: Components: "\(Cluster.name)/bank-userservice": {
|
||||
path: "projects/bank-of-holos/backend/components/bank-userservice"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
|
||||
#Platform: Components: "\(Cluster.name)/bank-ledger-db": {
|
||||
path: "projects/bank-of-holos/backend/components/bank-ledger-db"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
#Platform: Components: "\(Cluster.name)/bank-ledger-writer": {
|
||||
path: "projects/bank-of-holos/backend/components/bank-ledger-writer"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
#Platform: Components: "\(Cluster.name)/bank-balance-reader": {
|
||||
path: "projects/bank-of-holos/backend/components/bank-balance-reader"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
#Platform: Components: "\(Cluster.name)/bank-transaction-history": {
|
||||
path: "projects/bank-of-holos/backend/components/bank-transaction-history"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
#Platform: Components: "\(Cluster.name)/bank-contacts": {
|
||||
path: "projects/bank-of-holos/backend/components/bank-contacts"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package holos
|
||||
|
||||
// Platform wide definitions
|
||||
#BankOfHolos: {
|
||||
Name: "bank-of-holos"
|
||||
Frontend: Namespace: "bank-frontend"
|
||||
Backend: Namespace: "bank-backend"
|
||||
Security: Namespace: "bank-security"
|
||||
|
||||
// Resources to manage in each of the namespaces.
|
||||
Resources: #Resources
|
||||
}
|
||||
|
||||
// Register namespaces
|
||||
#Namespaces: (#BankOfHolos.Frontend.Namespace): _
|
||||
#Namespaces: (#BankOfHolos.Backend.Namespace): _
|
||||
#Namespaces: (#BankOfHolos.Security.Namespace): _
|
||||
|
||||
// Register projects
|
||||
#AppProjects: "bank-frontend": _
|
||||
#AppProjects: "bank-backend": _
|
||||
#AppProjects: "bank-security": _
|
||||
|
||||
// Register HTTPRoutes.
|
||||
// bank.example.com routes to Service frontend in the bank-frontend namespace.
|
||||
#HTTPRoutes: bank: _backendRefs: frontend: namespace: #BankOfHolos.Frontend.Namespace
|
||||
@@ -0,0 +1,3 @@
|
||||
package holos
|
||||
|
||||
#ArgoConfig: AppProject: #AppProjects["bank-backend"].metadata.name
|
||||
@@ -0,0 +1,122 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let BankName = #BankOfHolos.Name
|
||||
|
||||
let CommonLabels = {
|
||||
application: BankName
|
||||
environment: "development"
|
||||
team: "accounts"
|
||||
tier: "db"
|
||||
}
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-accounts-db"
|
||||
Namespace: #BankOfHolos.Backend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests/accounts-db.yaml
|
||||
Resources: {
|
||||
ConfigMap: "accounts-db-config": {
|
||||
apiVersion: "v1"
|
||||
data: {
|
||||
ACCOUNTS_DB_URI: "postgresql://accounts-admin:accounts-pwd@accounts-db:5432/accounts-db"
|
||||
POSTGRES_DB: "accounts-db"
|
||||
POSTGRES_PASSWORD: "accounts-pwd"
|
||||
POSTGRES_USER: "accounts-admin"
|
||||
}
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
labels: {
|
||||
app: "accounts-db"
|
||||
CommonLabels
|
||||
}
|
||||
name: "accounts-db-config"
|
||||
}
|
||||
}
|
||||
|
||||
Service: "accounts-db": {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata: {
|
||||
name: "accounts-db"
|
||||
labels: CommonLabels
|
||||
}
|
||||
spec: {
|
||||
ports: [{
|
||||
name: "tcp"
|
||||
port: 5432
|
||||
protocol: "TCP"
|
||||
targetPort: 5432
|
||||
}]
|
||||
selector: {
|
||||
app: "accounts-db"
|
||||
CommonLabels
|
||||
}
|
||||
type: "ClusterIP"
|
||||
}
|
||||
}
|
||||
|
||||
StatefulSet: "accounts-db": {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "StatefulSet"
|
||||
metadata: {
|
||||
name: "accounts-db"
|
||||
labels: CommonLabels
|
||||
}
|
||||
spec: {
|
||||
replicas: 1
|
||||
selector: matchLabels: {
|
||||
app: "accounts-db"
|
||||
CommonLabels
|
||||
}
|
||||
serviceName: "accounts-db"
|
||||
template: {
|
||||
metadata: labels: {
|
||||
app: "accounts-db"
|
||||
CommonLabels
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "accounts-db-config"
|
||||
}, {
|
||||
configMapRef: name: "demo-data-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/accounts-db:v0.6.5@sha256:abb955756a82b115e0fd9c5fa1527ae1a744b398b357fd6d7a26348feccad181"
|
||||
name: "accounts-db"
|
||||
ports: [{containerPort: 5432}]
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "250m"
|
||||
memory: "512Mi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
}
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/var/lib/postgresql/data"
|
||||
name: "postgresdb"
|
||||
subPath: "postgres"
|
||||
}]
|
||||
}]
|
||||
serviceAccount: BankName
|
||||
serviceAccountName: BankName
|
||||
volumes: [{
|
||||
emptyDir: {}
|
||||
name: "postgresdb"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let BankName = #BankOfHolos.Name
|
||||
|
||||
let CommonLabels = {
|
||||
application: BankName
|
||||
environment: "development"
|
||||
tier: "backend"
|
||||
}
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-backend-config"
|
||||
Namespace: #BankOfHolos.Backend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
Resources: [_]: [_]: metadata: labels: CommonLabels
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests/userservice.yaml
|
||||
Resources: {
|
||||
// Allow HTTPRoutes in the ingress gateway namespace to reference Services
|
||||
// in this namespace.
|
||||
ReferenceGrant: grant: #ReferenceGrant & {
|
||||
metadata: namespace: Namespace
|
||||
}
|
||||
|
||||
// Include shared resources
|
||||
#BankOfHolos.Resources
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let BankName = #BankOfHolos.Name
|
||||
|
||||
let CommonLabels = {
|
||||
application: BankName
|
||||
environment: "development"
|
||||
team: "ledger"
|
||||
tier: "backend"
|
||||
}
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-balance-reader"
|
||||
Namespace: #BankOfHolos.Backend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
Resources: [_]: [_]: metadata: labels: CommonLabels
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests
|
||||
Resources: {
|
||||
Service: balancereader: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata: {
|
||||
name: "balancereader"
|
||||
labels: CommonLabels
|
||||
}
|
||||
spec: {
|
||||
ports: [{
|
||||
name: "http"
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
}]
|
||||
selector: {
|
||||
app: "balancereader"
|
||||
CommonLabels
|
||||
}
|
||||
type: "ClusterIP"
|
||||
}
|
||||
}
|
||||
|
||||
Deployment: balancereader: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: {
|
||||
name: "balancereader"
|
||||
labels: CommonLabels
|
||||
}
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
app: "balancereader"
|
||||
CommonLabels
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
app: "balancereader"
|
||||
CommonLabels
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
env: [{
|
||||
name: "VERSION"
|
||||
value: "v0.6.5"
|
||||
}, {
|
||||
name: "PORT"
|
||||
value: "8080"
|
||||
}, {
|
||||
name: "ENABLE_TRACING"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "ENABLE_METRICS"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "POLL_MS"
|
||||
value: "100"
|
||||
}, {
|
||||
name: "CACHE_SIZE"
|
||||
value: "1000000"
|
||||
}, {
|
||||
name: "JVM_OPTS"
|
||||
value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Xms256m -Xmx512m"
|
||||
}, {
|
||||
name: "LOG_LEVEL"
|
||||
value: "info"
|
||||
}, {
|
||||
name: "NAMESPACE"
|
||||
valueFrom: fieldRef: fieldPath: "metadata.namespace"
|
||||
}]
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "ledger-db-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/balancereader:v0.6.5@sha256:de01f16554ae2d0b49ac85116e6307da8c0f8a35f50a0cf25e1e4a4fe18dca83"
|
||||
livenessProbe: {
|
||||
httpGet: {
|
||||
path: "/healthy"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
name: "balancereader"
|
||||
readinessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "500m"
|
||||
"ephemeral-storage": "0.5Gi"
|
||||
memory: "512Mi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "100m"
|
||||
"ephemeral-storage": "0.5Gi"
|
||||
memory: "256Mi"
|
||||
}
|
||||
}
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["all"]
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
}
|
||||
startupProbe: {
|
||||
failureThreshold: 30
|
||||
httpGet: {
|
||||
path: "/healthy"
|
||||
port: 8080
|
||||
}
|
||||
periodSeconds: 10
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/tmp"
|
||||
name: "tmp"
|
||||
}, {
|
||||
mountPath: "/tmp/.ssh"
|
||||
name: "publickey"
|
||||
readOnly: true
|
||||
}]
|
||||
}]
|
||||
securityContext: {
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
}
|
||||
serviceAccountName: BankName
|
||||
terminationGracePeriodSeconds: 5
|
||||
volumes: [{
|
||||
emptyDir: {}
|
||||
name: "tmp"
|
||||
}, {
|
||||
name: "publickey"
|
||||
secret: {
|
||||
items: [{
|
||||
key: "jwtRS256.key.pub"
|
||||
path: "publickey"
|
||||
}]
|
||||
secretName: "jwt-key"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let BankName = #BankOfHolos.Name
|
||||
|
||||
let CommonLabels = {
|
||||
application: BankName
|
||||
environment: "development"
|
||||
team: "accounts"
|
||||
tier: "backend"
|
||||
}
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-contacts"
|
||||
Namespace: #BankOfHolos.Backend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
Resources: [_]: [_]: metadata: labels: CommonLabels
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests
|
||||
Resources: {
|
||||
Service: contacts: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata: name: "contacts"
|
||||
spec: {
|
||||
ports: [{
|
||||
name: "http"
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
}]
|
||||
selector: {
|
||||
app: "contacts"
|
||||
CommonLabels
|
||||
}
|
||||
type: "ClusterIP"
|
||||
}
|
||||
}
|
||||
|
||||
Deployment: contacts: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: "contacts"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
app: "contacts"
|
||||
CommonLabels
|
||||
}
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: "contacts"
|
||||
CommonLabels
|
||||
}
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
env: [{
|
||||
name: "VERSION"
|
||||
value: "v0.6.5"
|
||||
}, {
|
||||
name: "PORT"
|
||||
value: "8080"
|
||||
}, {
|
||||
name: "ENABLE_TRACING"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "LOG_LEVEL"
|
||||
value: "info"
|
||||
}]
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "accounts-db-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/contacts:v0.6.5@sha256:e451dcac7d34a7bde979c7f02d4c7ebd83a77aff373e1131ce3a2bba2f7fdc1a"
|
||||
name: "contacts"
|
||||
readinessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "250m"
|
||||
"ephemeral-storage": "0.25Gi"
|
||||
memory: "128Mi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "100m"
|
||||
"ephemeral-storage": "0.25Gi"
|
||||
memory: "64Mi"
|
||||
}
|
||||
}
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["all"]
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/tmp"
|
||||
name: "tmp"
|
||||
}, {
|
||||
mountPath: "/tmp/.ssh"
|
||||
name: "publickey"
|
||||
readOnly: true
|
||||
}]
|
||||
}]
|
||||
securityContext: {
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
}
|
||||
serviceAccountName: BankName
|
||||
terminationGracePeriodSeconds: 5
|
||||
volumes: [{
|
||||
emptyDir: {}
|
||||
name: "tmp"
|
||||
}, {
|
||||
name: "publickey"
|
||||
secret: {
|
||||
items: [{
|
||||
key: "jwtRS256.key.pub"
|
||||
path: "publickey"
|
||||
}]
|
||||
secretName: "jwt-key"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let BankName = #BankOfHolos.Name
|
||||
|
||||
let CommonLabels = {
|
||||
application: BankName
|
||||
environment: "development"
|
||||
team: "ledger"
|
||||
tier: "db"
|
||||
}
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-ledger-db"
|
||||
Namespace: #BankOfHolos.Backend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests
|
||||
Resources: {
|
||||
ConfigMap: "ledger-db-config": {
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
name: "ledger-db-config"
|
||||
labels: {
|
||||
app: "ledger-db"
|
||||
CommonLabels
|
||||
}
|
||||
}
|
||||
data: {
|
||||
POSTGRES_DB: "postgresdb"
|
||||
POSTGRES_PASSWORD: "password"
|
||||
POSTGRES_USER: "admin"
|
||||
SPRING_DATASOURCE_PASSWORD: "password"
|
||||
SPRING_DATASOURCE_URL: "jdbc:postgresql://ledger-db:5432/postgresdb"
|
||||
SPRING_DATASOURCE_USERNAME: "admin"
|
||||
}
|
||||
}
|
||||
|
||||
Service: "ledger-db": {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata: {
|
||||
name: "ledger-db"
|
||||
labels: CommonLabels
|
||||
}
|
||||
spec: {
|
||||
ports: [{
|
||||
name: "tcp"
|
||||
port: 5432
|
||||
targetPort: 5432
|
||||
}]
|
||||
selector: {
|
||||
app: "ledger-db"
|
||||
CommonLabels
|
||||
}
|
||||
type: "ClusterIP"
|
||||
}
|
||||
}
|
||||
|
||||
StatefulSet: "ledger-db": {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "StatefulSet"
|
||||
metadata: {
|
||||
name: "ledger-db"
|
||||
labels: CommonLabels
|
||||
}
|
||||
spec: {
|
||||
replicas: 1
|
||||
selector: matchLabels: {
|
||||
app: "ledger-db"
|
||||
CommonLabels
|
||||
}
|
||||
serviceName: "ledger-db"
|
||||
template: {
|
||||
metadata: labels: {
|
||||
app: "ledger-db"
|
||||
CommonLabels
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "ledger-db-config"
|
||||
}, {
|
||||
configMapRef: name: "demo-data-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/ledger-db:v0.6.5@sha256:cc4fd25f301ab6d46b1312244d6931babc4c6cb66c5cb6d31d4a1adfa318a321"
|
||||
name: "postgres"
|
||||
ports: [{containerPort: 5432}]
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "250m"
|
||||
memory: "1Gi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "100m"
|
||||
memory: "512Mi"
|
||||
}
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/var/lib/postgresql/data"
|
||||
name: "postgresdb"
|
||||
subPath: "postgres"
|
||||
}]
|
||||
}]
|
||||
serviceAccountName: BankName
|
||||
volumes: [{
|
||||
emptyDir: {}
|
||||
name: "postgresdb"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let BankName = #BankOfHolos.Name
|
||||
|
||||
let CommonLabels = {
|
||||
application: BankName
|
||||
environment: "development"
|
||||
team: "ledger"
|
||||
tier: "backend"
|
||||
}
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-ledger-writer"
|
||||
Namespace: #BankOfHolos.Backend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
Resources: [_]: [_]: metadata: labels: CommonLabels
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests
|
||||
Resources: {
|
||||
Service: ledgerwriter: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata: {
|
||||
name: "ledgerwriter"
|
||||
labels: CommonLabels
|
||||
}
|
||||
spec: {
|
||||
ports: [{
|
||||
name: "http"
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
}]
|
||||
selector: {
|
||||
app: "ledgerwriter"
|
||||
CommonLabels
|
||||
}
|
||||
type: "ClusterIP"
|
||||
}
|
||||
}
|
||||
|
||||
Deployment: ledgerwriter: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: {
|
||||
name: "ledgerwriter"
|
||||
labels: CommonLabels
|
||||
}
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
app: "ledgerwriter"
|
||||
CommonLabels
|
||||
}
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: "ledgerwriter"
|
||||
CommonLabels
|
||||
}
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
env: [{
|
||||
name: "VERSION"
|
||||
value: "v0.6.5"
|
||||
}, {
|
||||
name: "PORT"
|
||||
value: "8080"
|
||||
}, {
|
||||
name: "ENABLE_TRACING"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "ENABLE_METRICS"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "JVM_OPTS"
|
||||
value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Xms256m -Xmx512m"
|
||||
}, {
|
||||
name: "LOG_LEVEL"
|
||||
value: "info"
|
||||
}, {
|
||||
name: "NAMESPACE"
|
||||
valueFrom: fieldRef: fieldPath: "metadata.namespace"
|
||||
}]
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "service-api-config"
|
||||
}, {
|
||||
configMapRef: name: "ledger-db-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/ledgerwriter:v0.6.5@sha256:5b66d6888b87993c8ebe260fe33005c4e4bc2bdae4b5682874e1a078d37ff3b2"
|
||||
name: "ledgerwriter"
|
||||
readinessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "500m"
|
||||
"ephemeral-storage": "0.5Gi"
|
||||
memory: "512Mi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "100m"
|
||||
"ephemeral-storage": "0.5Gi"
|
||||
memory: "256Mi"
|
||||
}
|
||||
}
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["all"]
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
}
|
||||
startupProbe: {
|
||||
failureThreshold: 30
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
periodSeconds: 10
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/tmp"
|
||||
name: "tmp"
|
||||
}, {
|
||||
mountPath: "/tmp/.ssh"
|
||||
name: "publickey"
|
||||
readOnly: true
|
||||
}]
|
||||
}]
|
||||
securityContext: {
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
}
|
||||
serviceAccountName: BankName
|
||||
terminationGracePeriodSeconds: 5
|
||||
volumes: [{
|
||||
emptyDir: {}
|
||||
name: "tmp"
|
||||
}, {
|
||||
name: "publickey"
|
||||
secret: {
|
||||
items: [{
|
||||
key: "jwtRS256.key.pub"
|
||||
path: "publickey"
|
||||
}]
|
||||
secretName: "jwt-key"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let BankName = #BankOfHolos.Name
|
||||
|
||||
let CommonLabels = {
|
||||
application: BankName
|
||||
environment: "development"
|
||||
team: "ledger"
|
||||
tier: "backend"
|
||||
}
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-transaction-history"
|
||||
Namespace: #BankOfHolos.Backend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
Resources: [_]: [_]: metadata: labels: CommonLabels
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests
|
||||
Resources: {
|
||||
Service: transactionhistory: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata: {
|
||||
labels: CommonLabels
|
||||
name: "transactionhistory"
|
||||
}
|
||||
spec: {
|
||||
ports: [{
|
||||
name: "http"
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
}]
|
||||
selector: {
|
||||
app: "transactionhistory"
|
||||
CommonLabels
|
||||
}
|
||||
type: "ClusterIP"
|
||||
}
|
||||
}
|
||||
|
||||
Deployment: transactionhistory: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: {
|
||||
name: "transactionhistory"
|
||||
labels: CommonLabels
|
||||
}
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
app: "transactionhistory"
|
||||
CommonLabels
|
||||
}
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: "transactionhistory"
|
||||
CommonLabels
|
||||
}
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
env: [{
|
||||
name: "VERSION"
|
||||
value: "v0.6.5"
|
||||
}, {
|
||||
name: "PORT"
|
||||
value: "8080"
|
||||
}, {
|
||||
name: "ENABLE_TRACING"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "ENABLE_METRICS"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "POLL_MS"
|
||||
value: "100"
|
||||
}, {
|
||||
name: "CACHE_SIZE"
|
||||
value: "1000"
|
||||
}, {
|
||||
name: "CACHE_MINUTES"
|
||||
value: "60"
|
||||
}, {
|
||||
name: "HISTORY_LIMIT"
|
||||
value: "100"
|
||||
}, {
|
||||
name: "JVM_OPTS"
|
||||
value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Xms256m -Xmx512m"
|
||||
}, {
|
||||
name: "LOG_LEVEL"
|
||||
value: "info"
|
||||
}, {
|
||||
name: "NAMESPACE"
|
||||
valueFrom: fieldRef: fieldPath: "metadata.namespace"
|
||||
}]
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "ledger-db-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/transactionhistory:v0.6.5@sha256:54a2b0866df44a50832e71b130f3e069fe8bbce71309fb6cf390b19f64d92c09"
|
||||
livenessProbe: {
|
||||
httpGet: {
|
||||
path: "/healthy"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
name: "transactionhistory"
|
||||
readinessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "500m"
|
||||
"ephemeral-storage": "0.5Gi"
|
||||
memory: "512Mi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "100m"
|
||||
"ephemeral-storage": "0.5Gi"
|
||||
memory: "256Mi"
|
||||
}
|
||||
}
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["all"]
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
}
|
||||
startupProbe: {
|
||||
failureThreshold: 30
|
||||
httpGet: {
|
||||
path: "/healthy"
|
||||
port: 8080
|
||||
}
|
||||
periodSeconds: 10
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/tmp"
|
||||
name: "tmp"
|
||||
}, {
|
||||
mountPath: "/tmp/.ssh"
|
||||
name: "publickey"
|
||||
readOnly: true
|
||||
}]
|
||||
}]
|
||||
securityContext: {
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
}
|
||||
serviceAccountName: BankName
|
||||
terminationGracePeriodSeconds: 5
|
||||
volumes: [{
|
||||
emptyDir: {}
|
||||
name: "tmp"
|
||||
}, {
|
||||
name: "publickey"
|
||||
secret: {
|
||||
items: [{
|
||||
key: "jwtRS256.key.pub"
|
||||
path: "publickey"
|
||||
}]
|
||||
secretName: "jwt-key"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let BankName = #BankOfHolos.Name
|
||||
|
||||
let CommonLabels = {
|
||||
application: BankName
|
||||
environment: "development"
|
||||
team: "accounts"
|
||||
tier: "backend"
|
||||
}
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-userservice"
|
||||
Namespace: #BankOfHolos.Backend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests/userservice.yaml
|
||||
Resources: {
|
||||
Service: userservice: {
|
||||
metadata: name: "userservice"
|
||||
metadata: labels: CommonLabels
|
||||
spec: {
|
||||
selector: {
|
||||
app: "userservice"
|
||||
CommonLabels
|
||||
}
|
||||
_ports: http: {
|
||||
name: "http"
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
protocol: "TCP"
|
||||
}
|
||||
ports: [for x in _ports {x}]
|
||||
}
|
||||
}
|
||||
|
||||
Deployment: userservice: {
|
||||
metadata: name: "userservice"
|
||||
metadata: labels: CommonLabels
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
app: "userservice"
|
||||
CommonLabels
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
app: "userservice"
|
||||
CommonLabels
|
||||
}
|
||||
spec: {
|
||||
serviceAccountName: BankName
|
||||
terminationGracePeriodSeconds: 5
|
||||
containers: [{
|
||||
env: [{
|
||||
name: "VERSION"
|
||||
value: "v0.6.5"
|
||||
}, {
|
||||
name: "PORT"
|
||||
value: "8080"
|
||||
}, {
|
||||
name: "ENABLE_TRACING"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "LOG_LEVEL"
|
||||
value: "info"
|
||||
}, {
|
||||
name: "TOKEN_EXPIRY_SECONDS"
|
||||
value: "3600"
|
||||
}, {
|
||||
name: "PRIV_KEY_PATH"
|
||||
value: "/tmp/.ssh/privatekey"
|
||||
}]
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "accounts-db-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/userservice:v0.6.5@sha256:f91e0e5bd6cdb16f6b867b2e3e874b23dd01f11592de006776f1dfb136702941"
|
||||
name: "userservice"
|
||||
ports: [{
|
||||
containerPort: 8080
|
||||
name: "http-server"
|
||||
}]
|
||||
readinessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "500m"
|
||||
"ephemeral-storage": "0.25Gi"
|
||||
memory: "256Mi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "260m"
|
||||
"ephemeral-storage": "0.25Gi"
|
||||
memory: "128Mi"
|
||||
}
|
||||
}
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["all"]
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/tmp"
|
||||
name: "tmp"
|
||||
}, {
|
||||
mountPath: "/tmp/.ssh"
|
||||
name: "keys"
|
||||
readOnly: true
|
||||
}]
|
||||
}]
|
||||
volumes: [{
|
||||
emptyDir: {}
|
||||
name: "tmp"
|
||||
}, {
|
||||
name: "keys"
|
||||
secret: {
|
||||
secretName: "jwt-key"
|
||||
items: [
|
||||
{
|
||||
key: "jwtRS256.key"
|
||||
path: "privatekey"
|
||||
},
|
||||
{
|
||||
key: "jwtRS256.key.pub"
|
||||
path: "publickey"
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package holos
|
||||
|
||||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
es "external-secrets.io/externalsecret/v1beta1"
|
||||
ss "external-secrets.io/secretstore/v1beta1"
|
||||
)
|
||||
|
||||
let BankName = #BankOfHolos.Name
|
||||
|
||||
#BankOfHolos: {
|
||||
// Resources to make available in each of the project namespaces.
|
||||
Resources: {
|
||||
ServiceAccount: (BankName): core.#ServiceAccount & {
|
||||
apiVersion: "v1"
|
||||
kind: "ServiceAccount"
|
||||
metadata: name: BankName
|
||||
}
|
||||
|
||||
// SecretStore to fetch secrets owned by the security team
|
||||
SecretStore: (BankName): ss.#SecretStore & {
|
||||
metadata: name: #BankOfHolos.Security.Namespace
|
||||
spec: provider: {
|
||||
kubernetes: {
|
||||
remoteNamespace: #BankOfHolos.Security.Namespace
|
||||
auth: serviceAccount: name: ServiceAccount[BankName].metadata.name
|
||||
server: {
|
||||
url: "https://kubernetes.default.svc"
|
||||
caProvider: {
|
||||
type: "ConfigMap"
|
||||
name: "kube-root-ca.crt"
|
||||
key: "ca.crt"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We do not check the private key into version control.
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/tree/v0.6.5/extras/jwt
|
||||
ExternalSecret: "jwt-key": es.#ExternalSecret & {
|
||||
metadata: name: "jwt-key"
|
||||
spec: {
|
||||
target: name: metadata.name
|
||||
dataFrom: [{extract: {key: metadata.name}}]
|
||||
refreshInterval: "5s"
|
||||
secretStoreRef: kind: "SecretStore"
|
||||
secretStoreRef: name: SecretStore[BankName].metadata.name
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests/config.yaml
|
||||
ConfigMap: "environment-config": core.#ConfigMap & {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: name: "environment-config"
|
||||
data: {
|
||||
LOCAL_ROUTING_NUM: "883745000"
|
||||
PUB_KEY_PATH: "/tmp/.ssh/publickey"
|
||||
}
|
||||
}
|
||||
|
||||
ConfigMap: "service-api-config": core.#ConfigMap & {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: name: "service-api-config"
|
||||
data: {
|
||||
TRANSACTIONS_API_ADDR: "ledgerwriter.\(#BankOfHolos.Backend.Namespace).svc:8080"
|
||||
BALANCES_API_ADDR: "balancereader.\(#BankOfHolos.Backend.Namespace).svc:8080"
|
||||
HISTORY_API_ADDR: "transactionhistory.\(#BankOfHolos.Backend.Namespace).svc:8080"
|
||||
CONTACTS_API_ADDR: "contacts.\(#BankOfHolos.Backend.Namespace).svc:8080"
|
||||
USERSERVICE_API_ADDR: "userservice.\(#BankOfHolos.Backend.Namespace).svc:8080"
|
||||
}
|
||||
}
|
||||
|
||||
ConfigMap: "demo-data-config": core.#ConfigMap & {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: name: "demo-data-config"
|
||||
data: {
|
||||
USE_DEMO_DATA: "True"
|
||||
DEMO_LOGIN_USERNAME: "testuser"
|
||||
// All demo user accounts are hardcoded to use the login password 'bankofanthos'
|
||||
DEMO_LOGIN_PASSWORD: "bankofanthos"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-frontend"
|
||||
Namespace: #BankOfHolos.Frontend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests/frontend.yaml
|
||||
Resources: {
|
||||
Service: frontend: {
|
||||
metadata: name: "frontend"
|
||||
metadata: labels: {
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
spec: {
|
||||
selector: {
|
||||
app: "frontend"
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
_ports: http: {
|
||||
name: "http"
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
protocol: "TCP"
|
||||
}
|
||||
ports: [for x in _ports {x}]
|
||||
}
|
||||
}
|
||||
|
||||
Deployment: frontend: {
|
||||
metadata: name: "frontend"
|
||||
metadata: labels: {
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
app: "frontend"
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
app: "frontend"
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
spec: {
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
}
|
||||
serviceAccountName: "bank-of-holos"
|
||||
terminationGracePeriodSeconds: 5
|
||||
containers: [{
|
||||
env: [{
|
||||
name: "BANK_NAME"
|
||||
value: "Bank of Holos"
|
||||
}, {
|
||||
name: "ENV_PLATFORM"
|
||||
value: "local"
|
||||
}, {
|
||||
name: "VERSION"
|
||||
value: "v0.6.5"
|
||||
}, {
|
||||
name: "PORT"
|
||||
value: "8080"
|
||||
}, {
|
||||
name: "ENABLE_TRACING"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "SCHEME"
|
||||
value: "https"
|
||||
}, {
|
||||
name: "LOG_LEVEL"
|
||||
value: "info"
|
||||
}, {
|
||||
name: "DEFAULT_USERNAME"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_LOGIN_USERNAME"
|
||||
name: "demo-data-config"
|
||||
}
|
||||
}, {
|
||||
name: "DEFAULT_PASSWORD"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_LOGIN_PASSWORD"
|
||||
name: "demo-data-config"
|
||||
}
|
||||
}, {
|
||||
name: "REGISTERED_OAUTH_CLIENT_ID"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_OAUTH_CLIENT_ID"
|
||||
name: "oauth-config"
|
||||
optional: true
|
||||
}
|
||||
}, {
|
||||
name: "ALLOWED_OAUTH_REDIRECT_URI"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_OAUTH_REDIRECT_URI"
|
||||
name: "oauth-config"
|
||||
optional: true
|
||||
}
|
||||
}]
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "service-api-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/frontend:v0.6.5@sha256:d72050f70d12383e4434ad04d189b681dc625f696087ddf0b5df641645c9dafa"
|
||||
livenessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 30
|
||||
}
|
||||
name: "front"
|
||||
readinessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "100m"
|
||||
memory: "64Mi"
|
||||
}
|
||||
}
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["all"]
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/tmp"
|
||||
name: "tmp"
|
||||
}, {
|
||||
mountPath: "/tmp/.ssh"
|
||||
name: "publickey"
|
||||
readOnly: true
|
||||
}]
|
||||
}]
|
||||
volumes: [
|
||||
{
|
||||
emptyDir: {}
|
||||
name: "tmp"
|
||||
},
|
||||
{
|
||||
name: "publickey"
|
||||
secret: {
|
||||
items: [{key: "jwtRS256.key.pub", path: "publickey"}]
|
||||
secretName: "jwt-key"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow HTTPRoutes in the ingress gateway namespace to reference Services
|
||||
// in this namespace.
|
||||
ReferenceGrant: grant: #ReferenceGrant & {
|
||||
metadata: namespace: Namespace
|
||||
}
|
||||
|
||||
// Include shared resources
|
||||
#BankOfHolos.Resources
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package holos
|
||||
|
||||
#ArgoConfig: AppProject: #AppProjects["bank-frontend"].metadata.name
|
||||
@@ -0,0 +1,165 @@
|
||||
package holos
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
)
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
// This may be useful to copy and generate other secrets.
|
||||
let SecretName = "jwt-key"
|
||||
|
||||
// Roles for reading and writing secrets
|
||||
let Reader = "\(SecretName)-reader"
|
||||
let Writer = "\(SecretName)-writer"
|
||||
|
||||
// AllowedName represents the service account allowed to read the generated
|
||||
// secret.
|
||||
let AllowedName = #BankOfHolos.Name
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-secrets"
|
||||
Namespace: #BankOfHolos.Security.Namespace
|
||||
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
Resources: [_]: [ID=string]: metadata: name: string | *ID
|
||||
|
||||
Resources: {
|
||||
// Kubernetes ServiceAccount used by the secret generator job.
|
||||
ServiceAccount: (Writer): corev1.#ServiceAccount
|
||||
// Role to allow the ServiceAccount to update secrets.
|
||||
Role: (Writer): rbacv1.#Role & {
|
||||
rules: [{
|
||||
apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["create", "update", "patch"]
|
||||
}]
|
||||
}
|
||||
// Bind the role to the service account.
|
||||
RoleBinding: (Writer): rbacv1.#RoleBinding & {
|
||||
roleRef: {
|
||||
apiGroup: "rbac.authorization.k8s.io"
|
||||
kind: "Role"
|
||||
name: Role[Writer].metadata.name
|
||||
}
|
||||
subjects: [{
|
||||
kind: "ServiceAccount"
|
||||
name: ServiceAccount[Writer].metadata.name
|
||||
namespace: Namespace
|
||||
}]
|
||||
}
|
||||
|
||||
let JobSpec = {
|
||||
serviceAccountName: Writer
|
||||
restartPolicy: "OnFailure"
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
runAsNonRoot: true
|
||||
runAsUser: 8192 // app
|
||||
}
|
||||
containers: [
|
||||
{
|
||||
name: "toolkit"
|
||||
image: "quay.io/holos-run/toolkit:2024-09-16"
|
||||
securityContext: {
|
||||
capabilities: drop: ["ALL"]
|
||||
allowPrivilegeEscalation: false
|
||||
}
|
||||
command: ["/bin/bash"]
|
||||
args: ["/config/entrypoint"]
|
||||
env: [{
|
||||
name: "HOME"
|
||||
value: "/tmp"
|
||||
}]
|
||||
volumeMounts: [{
|
||||
name: "config"
|
||||
mountPath: "/config"
|
||||
readOnly: true
|
||||
}]
|
||||
},
|
||||
]
|
||||
volumes: [{
|
||||
name: "config"
|
||||
configMap: name: Writer
|
||||
}]
|
||||
}
|
||||
|
||||
Job: (Writer): batchv1.#Job & {
|
||||
spec: template: spec: JobSpec
|
||||
}
|
||||
|
||||
ConfigMap: (Writer): corev1.#ConfigMap & {
|
||||
data: entrypoint: ENTRYPOINT
|
||||
}
|
||||
|
||||
// Allow the SecretStore in the frontend and backend namespaces to read the
|
||||
// secret.
|
||||
Role: (Reader): rbacv1.#Role & {
|
||||
rules: [{
|
||||
apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: [SecretName]
|
||||
verbs: ["get"]
|
||||
}]
|
||||
}
|
||||
|
||||
// Grant access to the bank-of-holos service account in the frontend and
|
||||
// backend namespaces.
|
||||
RoleBinding: (Reader): rbacv1.#RoleBinding & {
|
||||
roleRef: {
|
||||
apiGroup: "rbac.authorization.k8s.io"
|
||||
kind: "Role"
|
||||
name: Role[Reader].metadata.name
|
||||
}
|
||||
subjects: [{
|
||||
kind: "ServiceAccount"
|
||||
name: AllowedName
|
||||
namespace: #BankOfHolos.Frontend.Namespace
|
||||
}, {
|
||||
kind: "ServiceAccount"
|
||||
name: AllowedName
|
||||
namespace: #BankOfHolos.Backend.Namespace
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ENTRYPOINT = """
|
||||
#! /bin/bash
|
||||
#
|
||||
|
||||
tmpdir="$(mktemp -d)"
|
||||
finish() {
|
||||
status=$?
|
||||
rm -rf "${tmpdir}"
|
||||
return $status
|
||||
}
|
||||
trap finish EXIT
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$tmpdir"
|
||||
mkdir secret
|
||||
cd secret
|
||||
|
||||
echo "generating private key" >&2
|
||||
ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key -q -N "" -C \(AllowedName)
|
||||
echo "generating public key" >&2
|
||||
ssh-keygen -e -m PKCS8 -f jwtRS256.key > jwtRS256.key.pub
|
||||
cd ..
|
||||
|
||||
echo "copying secret into kubernetes manifest secret.yaml" >&2
|
||||
kubectl create secret generic \(SecretName) --from-file=secret --dry-run=client -o yaml > secret.yaml
|
||||
|
||||
echo "applying secret.yaml" >&2
|
||||
kubectl apply --server-side=true -f secret.yaml
|
||||
|
||||
echo "cleaning up" >&2
|
||||
rm -rf secret secret.yaml
|
||||
|
||||
echo "ok done" >&2
|
||||
"""
|
||||
@@ -0,0 +1,3 @@
|
||||
package holos
|
||||
|
||||
#ArgoConfig: AppProject: #AppProjects["bank-security"].metadata.name
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "bank-of-holos",
|
||||
"short": "demo bank composed of two projects",
|
||||
"long": "Bank of Holos is a sample HTTP-based web app that simulates a bank's payment processing network, allowing users to create artificial bank accounts and complete transactions."
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package holos
|
||||
|
||||
// Platform wide configuration
|
||||
#CertManager: {
|
||||
Version: "{{ .Version }}"
|
||||
Namespace: "{{ .Namespace }}"
|
||||
}
|
||||
|
||||
// Register the namespace
|
||||
#Namespaces: (#CertManager.Namespace): _
|
||||
|
||||
// Manage the component on every cluster in the platform
|
||||
for Fleet in #Fleets {
|
||||
for Cluster in Fleet.clusters {
|
||||
#Platform: Components: "\(Cluster.name)/{{ .Name }}": {
|
||||
path: "components/cert-manager"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package holos
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).BuildPlan
|
||||
|
||||
let Chart = {
|
||||
Name: "{{ .Name }}"
|
||||
Version: #CertManager.Version
|
||||
Namespace: #CertManager.Namespace
|
||||
|
||||
Repo: name: "{{ .RepoName }}"
|
||||
Repo: url: "{{ .RepoURL }}"
|
||||
|
||||
Values: installCRDs: true
|
||||
Values: startupapicheck: enabled: false
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "cert-manager",
|
||||
"short": "cloud native X.509 certificate management for kubernetes",
|
||||
"long": "cert-manager creates tls certificates for workloads in your kubernetes cluster and renews the certificates before they expire.",
|
||||
"chart": "",
|
||||
"reponame": "jetstack",
|
||||
"repourl": "https://charts.jetstack.io",
|
||||
"version": "1.15.3",
|
||||
"namespace": "cert-manager"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package holos
|
||||
|
||||
// Manage the component on every cluster in the platform
|
||||
for Fleet in #Fleets {
|
||||
for Cluster in Fleet.clusters {
|
||||
#Platform: Components: "\(Cluster.name)/external-secrets-crds": {
|
||||
path: "projects/platform/components/external-secrets-crds"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
#Platform: Components: "\(Cluster.name)/external-secrets": {
|
||||
path: "projects/platform/components/external-secrets"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package holos
|
||||
|
||||
// Platform wide configuration
|
||||
#ExternalSecrets: {
|
||||
Version: "{{ .Version }}"
|
||||
Namespace: "external-secrets"
|
||||
}
|
||||
|
||||
// Register the namespace
|
||||
#Namespaces: (#ExternalSecrets.Namespace): _
|
||||
@@ -0,0 +1,33 @@
|
||||
package holos
|
||||
|
||||
import (
|
||||
"encoding/yaml"
|
||||
ks "sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
(#Kubernetes & {Name: "external-secrets-crds"}).BuildPlan
|
||||
|
||||
// Holos stages BuildPlan resources as an intermediate step of the rendering
|
||||
// pipeline. The purpose is to provide the resources to kustomize for
|
||||
// post-processing.
|
||||
let BuildPlanResources = "build-plan-resources.yaml"
|
||||
|
||||
_Kustomization: ks.#Kustomization & {
|
||||
apiVersion: "kustomize.config.k8s.io/v1beta1"
|
||||
kind: "Kustomization"
|
||||
resources: [
|
||||
// Kustomize the intermediate build plan resources.
|
||||
BuildPlanResources,
|
||||
// Mix-in external resources.
|
||||
"https://raw.githubusercontent.com/external-secrets/external-secrets/v\(#ExternalSecrets.Version)/deploy/crds/bundle.yaml",
|
||||
]
|
||||
}
|
||||
|
||||
// Generate a kustomization.yaml directly from CUE so we can provide the correct
|
||||
// version.
|
||||
spec: components: kubernetesObjectsList: [{
|
||||
// intermediate build plan resources to kustomize. Necessary to activate the
|
||||
// kustomization post-rendering step in holos.
|
||||
kustomize: resourcesFile: BuildPlanResources
|
||||
kustomize: kustomizeFiles: "kustomization.yaml": yaml.Marshal(_Kustomization)
|
||||
}]
|
||||