Compare commits

..

1 Commits

Author SHA1 Message Date
Jeff McCune
8d2984a635 try release 2024-11-14 15:15:27 -07:00
699 changed files with 203725 additions and 15093 deletions

View File

@@ -29,15 +29,12 @@
"authpolicy",
"authproxy",
"authroutes",
"autoload",
"automount",
"automounting",
"autoscaler",
"balancereader",
"blackbox",
"buildplan",
"buildplans",
"Buildx",
"builtinpluginloadingoptions",
"cachedir",
"cadvisor",
@@ -46,7 +43,6 @@
"certificaterequest",
"certificaterequests",
"certificatesigningrequests",
"chartmuseum",
"clientset",
"clsx",
"clusterexternalsecret",
@@ -61,8 +57,6 @@
"Cmds",
"CNCF",
"CODEOWNERS",
"compinit",
"componentconfig",
"configdir",
"configmap",
"configmapargs",
@@ -75,7 +69,6 @@
"creds",
"crossplane",
"crunchydata",
"ctxt",
"cuecontext",
"cuelang",
"customresourcedefinition",
@@ -85,7 +78,6 @@
"destinationrules",
"devel",
"devicecode",
"distroless",
"dnsmasq",
"dscacheutil",
"ecrauthorizationtoken",
@@ -104,7 +96,6 @@
"fieldmaskpb",
"fieldspec",
"flushcache",
"fluxcd",
"fullname",
"gatewayclass",
"gatewayclasses",
@@ -158,7 +149,6 @@
"jetstack",
"jiralert",
"Jsonnet",
"Kargo",
"kfbh",
"killall",
"kubeadm",
@@ -196,7 +186,6 @@
"mutatingwebhookconfigurations",
"mvdan",
"mxcl",
"mychart",
"myhostname",
"myRegistrKeySecretName",
"mysecret",
@@ -283,7 +272,6 @@
"serviceentries",
"serviceentry",
"servicemonitor",
"sigstore",
"somevalue",
"SOMEVAR",
"sortoptions",
@@ -314,17 +302,14 @@
"tokencache",
"Tokener",
"tolerations",
"TOPLEVEL",
"Traceid",
"traefik",
"transactionhistory",
"tsdb",
"txtar",
"typemeta",
"udev",
"uibutton",
"Unmarshal",
"unshallow",
"unstage",
"untar",
"upbound",

View File

@@ -1,131 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: NeedsInvestigation, Triage
assignees: ''
---
<!--
Please answer these questions before submitting your issue. Thanks!
To ask questions, see https://github.com/holos-run/holos/discussions
-->
### What version of holos are you using (`holos --version`)?
```
0.0.0
```
### Does this issue reproduce with the latest release?
<!--
Get the latest release with:
brew install holos-run/tap/holos
Or see https://holos.run/docs/v1alpha5/tutorial/setup/
-->
### What did you do?
<!--
Please provide a testscript that should pass, but does not because of the bug.
See the below example.
You can create a txtar from a directory with:
holos txtar ./path/to/dir
Refer to: https://github.com/rogpeppe/go-internal/tree/master/cmd/testscript
-->
Steps to reproduce:
```shell
testscript -v -continue <<EOF
```
```txtar
# Have: an error related to the imported Kustomize schemas.
# Want: holos show buildplans to work.
exec holos --version
exec holos init platform v1alpha5 --force
# remove the fix to trigger the bug
rm cue.mod/pkg/sigs.k8s.io/kustomize/api/types/var.cue
# want a BuildPlan shown
exec holos show buildplans
cmp stdout buildplan.yaml
# want this error to go away
! stderr 'cannot convert non-concrete value string'
-- buildplan.yaml --
kind: BuildPlan
-- platform/example.cue --
package holos
Platform: Components: example: {
name: "example"
path: "components/example"
}
-- components/example/example.cue --
package holos
import "encoding/yaml"
holos: Component.BuildPlan
Component: #Kustomize & {
KustomizeConfig: Kustomization: patches: [
{
target: kind: "CustomResourceDefinition"
patch: yaml.Marshal([{
op: "add"
path: "/metadata/annotations/example"
value: "example-value"
}])
},
]
}
```
```shell
EOF
```
### What did you expect to see?
The testscript should pass.
### What did you see instead?
The testscript fails because of the bug.
```txt
# Have: an error related to the imported Kustomize schemas.
# Want: holos show buildplans to work. (0.168s)
> exec holos --version
[stdout]
0.100.1-2-g9b10e23-dirty
> exec holos init platform v1alpha5 --force
# remove the fix to trigger the bug (0.000s)
> rm cue.mod/pkg/sigs.k8s.io/kustomize/api/types/var.cue
# want a BuildPlan shown (0.091s)
> exec holos show buildplans
[stderr]
could not run: holos.spec.artifacts.0.transformers.0.kustomize.kustomization.patches.0.target.name: cannot convert non-concrete value string at builder/v1alpha5/builder.go:218
holos.spec.artifacts.0.transformers.0.kustomize.kustomization.patches.0.target.name: cannot convert non-concrete value string:
$WORK/cue.mod/gen/sigs.k8s.io/kustomize/api/types/var_go_gen.cue:33:2
[exit status 1]
FAIL: <stdin>:8: unexpected command failure
> cmp stdout buildplan.yaml
diff stdout buildplan.yaml
--- stdout
+++ buildplan.yaml
@@ -0,0 +1,1 @@
+kind: BuildPlan
FAIL: <stdin>:9: stdout and buildplan.yaml differ
# want this error to go away (0.000s)
> ! stderr 'cannot convert non-concrete value string'
FAIL: <stdin>:11: unexpected match for `cannot convert non-concrete value string` found in stderr: cannot convert non-concrete value string
failed run
```

View File

@@ -1,143 +0,0 @@
name: Container
# Only allow actors with write permission to the repository to trigger this
# workflow.
permissions:
contents: write
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
git_ref:
description: 'Git ref to build (e.g., refs/tags/v1.2.3, refs/heads/main)'
required: true
type: string
jobs:
buildx:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Set tag from trigger event
id: opts
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "ref=${{ inputs.git_ref }}" >> $GITHUB_OUTPUT
else
echo "ref=${GITHUB_REF}" >> $GITHUB_OUTPUT
fi
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ steps.opts.outputs.ref }}
- name: SHA
id: sha
run: echo "sha=$(/usr/bin/git log -1 --format='%H')" >> $GITHUB_OUTPUT
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Fetch tags
run: git fetch --prune --unshallow --tags
- name: Set Tags
id: tags
run: |
echo "detail=$(/usr/bin/git describe --tags HEAD)" >> $GITHUB_OUTPUT
echo "suffix=$(test -n "$(git status --porcelain)" && echo '-dirty' || echo '')" >> $GITHUB_OUTPUT
echo "tag=$(/usr/bin/git describe --tags HEAD)$(test -n "$(git status --porcelain)" && echo '-dirty' || echo '')" >> $GITHUB_OUTPUT
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push container images
id: build-and-push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/holos-run/holos:${{ steps.tags.outputs.tag }}
ghcr.io/holos-run/holos:${{ steps.sha.outputs.sha }}${{ steps.tags.outputs.suffix }}
- name: Setup Cosign to sign container images
uses: sigstore/cosign-installer@v3.7.0
- name: Sign with GitHub OIDC Token
env:
DIGEST: ${{ steps.build-and-push.outputs.digest }}
run: |
cosign sign --yes ghcr.io/holos-run/holos:${{ steps.tags.outputs.tag }}@${DIGEST}
cosign sign --yes ghcr.io/holos-run/holos:${{ steps.sha.outputs.sha }}${{ steps.tags.outputs.suffix }}@${DIGEST}
- uses: actions/create-github-app-token@v1
id: app-token
with:
owner: ${{ github.repository_owner }}
app-id: ${{ vars.GORELEASER_APP_ID }}
private-key: ${{ secrets.GORELEASER_APP_PRIVATE_KEY }}
- name: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
- run: |
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
- name: Update holos-run/holos-action
env:
IMAGE: ghcr.io/holos-run/holos:v0.102.1
VERSION: ${{ steps.tags.outputs.tag }}
USER_ID: ${{ steps.get-user-id.outputs.user-id }}
TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
git clone "https://github.com/holos-run/holos-action"
cd holos-action
git remote set-url origin https://${USER_ID}:${TOKEN}@github.com/holos-run/holos-action
docker pull --quiet "${IMAGE}"
docker run -v $(pwd):/app --workdir /app --rm "${IMAGE}" \
holos cue export --out yaml action.cue -t "version=${VERSION}" > action.yml
git add action.yml
git commit -m "ci: update holos to ${VERSION} - https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" || (echo "No changes to commit"; exit 0)
git push origin HEAD:main HEAD:v0 HEAD:v1
- name: Login to quay.io
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USER }}
password: ${{ secrets.QUAY_TOKEN }}
- name: Push to quay.io
env:
DIGEST: ${{ steps.build-and-push.outputs.digest }}
run: |
# docker push quay.io/holos-run/holos:${{ steps.tags.outputs.tag }}
docker pull --quiet ghcr.io/holos-run/holos:${{ steps.tags.outputs.tag }}@${DIGEST}
docker tag ghcr.io/holos-run/holos:${{ steps.tags.outputs.tag }}@${DIGEST} \
quay.io/holos-run/holos:${{ steps.tags.outputs.tag }}
docker push quay.io/holos-run/holos:${{ steps.tags.outputs.tag }}
docker pull --quiet ghcr.io/holos-run/holos:${{ steps.sha.outputs.sha }}${{ steps.tags.outputs.suffix }}@${DIGEST}
docker tag ghcr.io/holos-run/holos:${{ steps.sha.outputs.sha }}${{ steps.tags.outputs.suffix }}@${DIGEST} \
quay.io/holos-run/holos:${{ steps.sha.outputs.sha }}${{ steps.tags.outputs.suffix }}
docker push quay.io/holos-run/holos:${{ steps.sha.outputs.sha }}${{ steps.tags.outputs.suffix }}
- name: Sign quay.io image
env:
DIGEST: ${{ steps.build-and-push.outputs.digest }}
run: |
cosign sign --yes quay.io/holos-run/holos:${{ steps.tags.outputs.tag }}@${DIGEST}
cosign sign --yes quay.io/holos-run/holos:${{ steps.sha.outputs.sha }}${{ steps.tags.outputs.suffix }}@${DIGEST}
outputs:
tag: ${{ steps.tags.outputs.tag }}
detail: ${{ steps.tags.outputs.detail }}

View File

@@ -2,7 +2,7 @@ name: Dev Deploy
on:
push:
branches: ['dev-deploy']
branches: ['main', 'dev-deploy']
jobs:
deploy:

View File

@@ -1,5 +1,6 @@
---
name: Spelling
# https://github.com/golangci/golangci-lint-action?tab=readme-ov-file#how-to-use
name: Lint
"on":
push:
branches:
@@ -7,11 +8,35 @@ name: Spelling
- test
pull_request:
types: [opened, synchronize]
permissions:
contents: read
jobs:
cspell:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./hack/cspell
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: stable
## Not needed on ubuntu-latest
# - name: Install Packages
# run: sudo apt update && sudo apt -qq -y install git curl zip unzip tar bzip2 make
- name: Install Tools
run: make tools
- name: Lint
# golangci-lint runs in a separate workflow.
run: make lint -o golangci-lint

View File

@@ -64,7 +64,8 @@ jobs:
app-id: ${{ vars.GORELEASER_APP_ID }}
private-key: ${{ secrets.GORELEASER_APP_PRIVATE_KEY }}
- name: Run GoReleaser
- name: Run GoReleaser if tag
if: github.ref_type == 'tag'
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
@@ -73,3 +74,14 @@ jobs:
env:
HOMEBREW_TAP_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser if branch
if: github.ref_type == 'branch' && github.ref == 'refs/heads/release'
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: '~> v2'
args: release --clean --nightly
env:
HOMEBREW_TAP_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -28,11 +28,19 @@ jobs:
with:
go-version: stable
- name: Install Packages
run: sudo apt update && sudo apt -qq -y install git curl zip unzip tar bzip2 make
- name: Set up Helm
uses: azure/setup-helm@v4
- name: Set up Kubectl
uses: azure/setup-kubectl@v4
- name: Install Tools
run: |
set -x
make tools
- name: Test
run: ./scripts/test

View File

@@ -25,6 +25,24 @@ builds:
- amd64
- arm64
# .goreleaser.yml
nightly:
# Default: `{{ incpatch .Version }}-{{ .ShortCommit }}-nightly`.
# Templates: allowed.
version_template: "{{ .Version }}-{{ .ShortCommit }}-devel"
# Tag name to create if publish_release is enabled.
tag_name: devel
# Whether to publish a release or not.
# Only works on GitHub.
publish_release: true
# Whether to delete previous pre-releases for the same `tag_name` when
# releasing.
# This allows you to keep a single pre-release.
keep_single_release: true
signs:
- artifacts: checksum
args: ["-u", "code-signing-key@openinfrastructure.co", "--output", "${signature}", "--detach-sign", "${artifact}"]
@@ -81,8 +99,8 @@ brews:
bash_output = Utils.safe_popen_read(bin/"holos", "completion", "bash")
(bash_completion/"holos").write bash_output
zsh_output = Utils.safe_popen_read(bin/"holos", "completion", "zsh")
(zsh_completion/"_holos").write zsh_output
(zsh_completion/"holos").write zsh_output
fish_output = Utils.safe_popen_read(bin/"holos", "completion", "fish")
(fish_completion/"holos.fish").write fish_output
test: |
system "#{bin}/holos --version"
system "#{bin}/holos version"

View File

@@ -1,31 +1,8 @@
FROM registry.k8s.io/kubectl:v1.31.0 AS kubectl
# https://github.com/GoogleContainerTools/distroless
FROM golang:1.23 AS build
WORKDIR /go/src/app
COPY . .
RUN CGO_ENABLED=0 make install
RUN CGO_ENABLED=0 go install sigs.k8s.io/kustomize/kustomize/v5
# Install helm to /usr/local/bin/helm
# https://helm.sh/docs/intro/install/#from-script
# https://holos.run/docs/v1alpha5/tutorial/setup/#dependencies
RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 \
&& chmod 700 get_helm.sh \
&& DESIRED_VERSION=v3.16.2 ./get_helm.sh \
&& rm -f get_helm.sh
COPY --from=kubectl /bin/kubectl /usr/local/bin/
# distroless
FROM gcr.io/distroless/static-debian12 AS final
COPY --from=build \
/go/bin/holos \
/go/bin/kustomize \
/usr/local/bin/kubectl \
/usr/local/bin/helm \
/bin/
# Usage: docker run -v $(pwd):/app --workdir /app --rm -it quay.io/holos-run/holos holos render platform
CMD ["/bin/holos"]
FROM quay.io/holos-run/debian:bullseye AS final
USER root
WORKDIR /app
ADD bin bin
RUN chown -R app: /app
# Kubernetes requires the user to be numeric
USER 8192
ENTRYPOINT bin/holos server

129
README.md
View File

@@ -1,130 +1,35 @@
# Holos
## Holos - A Holistic Development Platform
<img width="50%"
align="right"
style="display: block; margin: 40px auto;"
src="https://openinfrastructure.co/blog/2016/02/27/logo/logorectangle.png">
[Holos] is a configuration management tool for Kubernetes implementing the
[rendered manifests pattern]. It handles configurations ranging from single
resources to multi-cluster platforms across regions.
Building and maintaining a software development platform is a complex and time
consuming endeavour. Organizations often dedicate a team of 3-4 who need 6-12
months to build the platform.
Key components:
- Platform schemas defining component integration
- Building blocks unifying Helm, Kustomize and Kubernetes configs with CUE
- BuildPlan pipeline for generating, transforming and validating manifests
Holos is a tool and a reference platform to reduce the complexity and speed up
the process of building a modern, cloud native software development platform.
```mermaid
---
title: Rendering Overview
---
graph LR
Platform[<a href="https://holos.run/docs/v1alpha5/api/author/#Platform">Platform</a>]
Component[<a href="https://holos.run/docs/v1alpha5/api/author/#ComponentConfig">Components</a>]
- **Accelerate new projects** - Reduce time to market and operational complexity by starting your new project on top of the Holos reference platform.
- **Modernize existing projects** - Incrementally incorporate your existing platform services into Holos for simpler integration.
- **Unified configuration model** - Increase safety and reduce the risk of config changes with CUE.
- **First class Helm and Kustomize support** - Leverage and reuse your existing investment in existing configuration tools such as Helm and Kustomize.
- **Modern Authentication and Authorization** - Holos seamlessly integrates platform identity and access management with zero-trust beyond corp style authorization policy.
Helm[<a href="https://holos.run/docs/v1alpha5/api/author/#Helm">Helm</a>]
Kustomize[<a href="https://holos.run/docs/v1alpha5/api/author/#Kustomize">Kustomize</a>]
Kubernetes[<a href="https://holos.run/docs/v1alpha5/api/author/#Kubernetes">Kubernetes</a>]
## Quick Installation
BuildPlan[<a href="https://holos.run/docs/v1alpha5/api/core/#BuildPlan">BuildPlan</a>]
ResourcesArtifact[<a href="https://holos.run/docs/v1alpha5/api/core/#Artifact">Resources<br/>Artifact</a>]
GitOpsArtifact[<a href="https://holos.run/docs/v1alpha5/api/core/#Artifact">GitOps<br/>Artifact</a>]
Generators[<a href="https://holos.run/docs/v1alpha5/api/core/#Generator">Generators</a>]
Transformers[<a href="https://holos.run/docs/v1alpha5/api/core/#Transformer">Transformers</a>]
Validators[<a href="https://holos.run/docs/v1alpha5/api/core/#Validator">Validators</a>]
Files[Manifest<br/>Files]
Platform --> Component
Component --> Helm --> BuildPlan
Component --> Kubernetes --> BuildPlan
Component --> Kustomize --> BuildPlan
BuildPlan --> ResourcesArtifact --> Generators
BuildPlan --> GitOpsArtifact --> Generators
Generators --> Transformers --> Validators --> Files
```console
go install github.com/holos-run/holos/cmd/holos@latest
```
## Setup
## Docs and Support
```shell
brew install holos-run/tap/holos
```
The documentation for developing and using Holos is available at: https://holos.run
Refer to [setup] for other installation methods and dependencies.
## Example
See our [tutorial] for a complete hello world example.
```cue showLineNumbers
package holos
holos: Component.BuildPlan
Component: #Helm & {
Name: "podinfo"
Chart: {
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
Values: ui: {
message: string | *"Hello World" @tag(message, type=string)
}
}
```
## Organizational Role
Platform engineers use Holos to generate Kubernetes manifests, both locally and
in CI pipelines. The manifests are committed to version control and deployed via
GitOps tools like ArgoCD or Flux.
Holos integrates seamlessly with existing Helm charts, Kustomize bases, and
other version-controlled configurations.
## Advantages of Holos
### Safe
Holos leverages [CUE] for strong typing and validation of configuration data,
ensuring consistent output from Helm and other tools.
### Consistent
A unified pipeline processes all configurations - whether from CUE, Helm, or
Kustomize - through the same well-defined stages.
### Flexible
Composable building blocks for generation, transformation, validation and
integration let teams assemble workflows that match their needs.
The core is intentionally unopinionated about platform configuration patterns.
Common needs like environments and clusters are provided as customizable
[topics] recipes rather than enforced structures.
## Getting Help
Get support through our [Discord] channel or [GitHub discussions]. Configuration
challenges arise at all experience levels - we welcome your questions and are
here to help.
For discussion and support, [open a discussion](https://github.com/orgs/holos-run/discussions/new/choose).
## License
Holos is licensed under Apache 2.0 as found in the [LICENSE file](LICENSE).
[Holos]: https://holos.run/docs/overview/
[rendered manifests pattern]: https://akuity.io/blog/the-rendered-manifests-pattern
[CUE]: https://cuelang.org/
[Discord]: https://discord.gg/JgDVbNpye7
[GitHub discussions]: https://github.com/holos-run/holos/discussions
[Why CUE for Configuration]: https://holos.run/blog/why-cue-for-configuration/
[tutorial]: https://holos.run/docs/overview/
[setup]: https://holos.run/docs/setup/
[topics]: https://holos.run/docs/topics/

View File

@@ -46,11 +46,6 @@ type ComponentConfig struct {
// Name represents the BuildPlan metadata.name field. Used to construct the
// fully rendered manifest file path.
Name string
// Labels represent the BuildPlan metadata.labels field.
Labels map[string]string
// Annotations represent the BuildPlan metadata.annotations field.
Annotations map[string]string
// Path represents the path to the component producing the BuildPlan.
Path string
// Parameters are useful to reuse a component with various parameters.
@@ -65,10 +60,8 @@ type ComponentConfig struct {
// Resources represents kubernetes resources mixed into the rendered manifest.
Resources core.Resources
// KustomizeConfig represents the kustomize configuration.
// KustomizeConfig represents the configuration kustomize.
KustomizeConfig KustomizeConfig
// Validators represent checks that must pass for output to be written.
Validators map[NameLabel]core.Validator
// Artifacts represents additional artifacts to mix in. Useful for adding
// GitOps resources. Each Artifact is unified without modification into the
// BuildPlan.

View File

@@ -23,21 +23,31 @@ package core
// [external credential provider]: https://github.com/kubernetes/enhancements/blob/313ad8b59c80819659e1fbf0f165230f633f2b22/keps/sig-auth/541-external-credential-providers/README.md
type BuildPlan struct {
// Kind represents the type of the resource.
Kind string `json:"kind" yaml:"kind" cue:"\"BuildPlan\""`
Kind string `json:"kind" cue:"\"BuildPlan\""`
// APIVersion represents the versioned schema of the resource.
APIVersion string `json:"apiVersion" yaml:"apiVersion" cue:"string | *\"v1alpha5\""`
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha5\""`
// Metadata represents data about the resource such as the Name.
Metadata Metadata `json:"metadata" yaml:"metadata"`
Metadata Metadata `json:"metadata"`
// Spec specifies the desired state of the resource.
Spec BuildPlanSpec `json:"spec" yaml:"spec"`
Spec BuildPlanSpec `json:"spec"`
// Source reflects the origin of the BuildPlan.
Source BuildPlanSource `json:"source,omitempty"`
}
// BuildPlanSpec represents the specification of the [BuildPlan].
type BuildPlanSpec struct {
// Artifacts represents the artifacts for holos to build.
Artifacts []Artifact `json:"artifacts" yaml:"artifacts"`
Artifacts []Artifact `json:"artifacts"`
// Disabled causes the holos cli to disregard the build plan.
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
Disabled bool `json:"disabled,omitempty"`
}
// BuildPlanSource reflects the origin of a [BuildPlan]. Useful to save a build
// plan to a file, then re-generate it without needing to process a [Platform]
// component collection.
type BuildPlanSource struct {
// Component reflects the component that produced the build plan.
Component Component `json:"component,omitempty"`
}
// Artifact represents one fully rendered manifest produced by a [Transformer]
@@ -61,11 +71,10 @@ type BuildPlanSpec struct {
// Transformers to produce the same Output value within the context of a
// [BuildPlan].
type Artifact struct {
Artifact FilePath `json:"artifact,omitempty" yaml:"artifact,omitempty"`
Generators []Generator `json:"generators,omitempty" yaml:"generators,omitempty"`
Transformers []Transformer `json:"transformers,omitempty" yaml:"transformers,omitempty"`
Validators []Validator `json:"validators,omitempty" yaml:"validators,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
Artifact FilePath `json:"artifact,omitempty"`
Generators []Generator `json:"generators,omitempty"`
Transformers []Transformer `json:"transformers,omitempty"`
Skip bool `json:"skip,omitempty"`
}
// Generator generates Kubernetes resources. [Helm] and [Resources] are the
@@ -81,19 +90,19 @@ type Artifact struct {
// 3. [File] - Generates data by reading a file from the component directory.
type Generator struct {
// Kind represents the kind of generator. Must be Resources, Helm, or File.
Kind string `json:"kind" yaml:"kind" cue:"\"Resources\" | \"Helm\" | \"File\""`
Kind string `json:"kind" cue:"\"Resources\" | \"Helm\" | \"File\""`
// Output represents a file for a Transformer or Artifact to consume.
Output FilePath `json:"output" yaml:"output"`
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" yaml:"resources,omitempty"`
Resources Resources `json:"resources,omitempty"`
// Helm generator. Ignored unless kind is Helm.
Helm Helm `json:"helm,omitempty" yaml:"helm,omitempty"`
Helm Helm `json:"helm,omitempty"`
// File generator. Ignored unless kind is File.
File File `json:"file,omitempty" yaml:"file,omitempty"`
File File `json:"file,omitempty"`
}
// Resource represents one kubernetes api object.
@@ -110,24 +119,24 @@ type Resources map[Kind]map[InternalLabel]Resource
// multiple resources.
type File struct {
// Source represents a file sub-path relative to the component path.
Source FilePath `json:"source" yaml:"source"`
Source FilePath `json:"source"`
}
// Helm represents a [Chart] manifest [Generator].
type Helm struct {
// Chart represents a helm chart to manage.
Chart Chart `json:"chart" yaml:"chart"`
Chart Chart `json:"chart"`
// Values represents values for holos to marshal into values.yaml when
// rendering the chart.
Values Values `json:"values" yaml:"values"`
Values Values `json:"values"`
// EnableHooks enables helm hooks when executing the `helm template` command.
EnableHooks bool `json:"enableHooks,omitempty" yaml:"enableHooks,omitempty"`
EnableHooks bool `json:"enableHooks,omitempty"`
// Namespace represents the helm namespace flag
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Namespace string `json:"namespace,omitempty"`
// APIVersions represents the helm template --api-versions flag
APIVersions []string `json:"apiVersions,omitempty" yaml:"apiVersions,omitempty"`
APIVersions []string `json:"apiVersions,omitempty"`
// KubeVersion represents the helm template --kube-version flag
KubeVersion string `json:"kubeVersion,omitempty" yaml:"kubeVersion,omitempty"`
KubeVersion string `json:"kubeVersion,omitempty"`
}
// Values represents [Helm] Chart values generated from CUE.
@@ -136,36 +145,19 @@ type Values map[string]any
// Chart represents a [Helm] Chart.
type Chart struct {
// Name represents the chart name.
Name string `json:"name" yaml:"name"`
Name string `json:"name"`
// Version represents the chart version.
Version string `json:"version" yaml:"version"`
Version string `json:"version"`
// Release represents the chart release when executing helm template.
Release string `json:"release" yaml:"release"`
Release string `json:"release"`
// Repository represents the repository to fetch the chart from.
Repository Repository `json:"repository,omitempty" yaml:"repository,omitempty"`
Repository Repository `json:"repository,omitempty"`
}
// Repository represents a [Helm] [Chart] repository.
//
// The Auth field is useful to configure http basic authentication to the Helm
// repository. Holos gets the username and password from the environment
// variables represented by the Auth field.
type Repository struct {
Name string `json:"name" yaml:"name"`
URL string `json:"url" yaml:"url"`
Auth Auth `json:"auth,omitempty" yaml:"auth,omitempty"`
}
// Auth represents environment variable names containing auth credentials.
type Auth struct {
Username AuthSource `json:"username" yaml:"username"`
Password AuthSource `json:"password" yaml:"password"`
}
// AuthSource represents a source for the value of an [Auth] field.
type AuthSource struct {
Value string `json:"value,omitempty" yaml:"value,omitempty"`
FromEnv string `json:"fromEnv,omitempty" yaml:"fromEnv,omitempty"`
Name string `json:"name"`
URL string `json:"url"`
}
// Transformer combines multiple inputs from prior [Generator] or [Transformer]
@@ -179,17 +171,17 @@ type AuthSource struct {
// [Introduction to Kustomize]: https://kubectl.docs.kubernetes.io/guides/config_management/introduction/
type Transformer struct {
// Kind represents the kind of transformer. Must be Kustomize, or Join.
Kind string `json:"kind" yaml:"kind" cue:"\"Kustomize\" | \"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" yaml:"inputs"`
Inputs []FilePath `json:"inputs"`
// Output represents a file for a subsequent Transformer or Artifact to
// consume.
Output FilePath `json:"output" yaml:"output"`
Output FilePath `json:"output"`
// Kustomize transformer. Ignored unless kind is Kustomize.
Kustomize Kustomize `json:"kustomize,omitempty" yaml:"kustomize,omitempty"`
Kustomize Kustomize `json:"kustomize,omitempty"`
// Join transformer. Ignored unless kind is Join.
Join Join `json:"join,omitempty" yaml:"join,omitempty"`
Join Join `json:"join,omitempty"`
}
// Join represents a [Transformer] using [bytes.Join] to concatenate multiple
@@ -199,15 +191,15 @@ type Transformer struct {
//
// [bytes.Join]: https://pkg.go.dev/bytes#Join
type Join struct {
Separator string `json:"separator,omitempty" yaml:"separator,omitempty"`
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" yaml:"kustomization"`
Kustomization Kustomization `json:"kustomization"`
// Files holds file contents for kustomize, e.g. patch files.
Files FileContentMap `json:"files,omitempty" yaml:"files,omitempty"`
Files FileContentMap `json:"files,omitempty"`
}
// Kustomization represents a kustomization.yaml file for use with the
@@ -216,37 +208,15 @@ type Kustomize struct {
// 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
// FileContent represents file contents.
type FileContent string
// Validator validates files. Useful to validate an [Artifact] prior to writing
// it out to the final destination. Holos may execute validators concurrently.
// See the [validators] tutorial for an end to end example.
//
// [validators]: https://holos.run/docs/v1alpha5/tutorial/validators/
type Validator struct {
// Kind represents the kind of transformer. Must be Kustomize, or Join.
Kind string `json:"kind" yaml:"kind" cue:"\"Command\""`
// Inputs represents the files to validate. Usually the final Artifact.
Inputs []FilePath `json:"inputs" yaml:"inputs"`
// Command represents a validation command. Ignored unless kind is Command.
Command Command `json:"command,omitempty" yaml:"command,omitempty"`
}
// Command represents a command vetting one or more artifacts. Holos appends
// fully qualified input file paths to the end of the args list, then executes
// the command. Inputs are written into a temporary directory prior to
// executing the command and removed afterwards.
type Command struct {
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
}
// 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
@@ -259,13 +229,7 @@ type Kind string
// Metadata represents data about the resource such as the Name.
type Metadata struct {
// Name represents the resource name.
Name string `json:"name" yaml:"name"`
// Labels represents a resource selector.
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
// Annotations represents arbitrary non-identifying metadata. For example
// holos uses the `app.holos.run/description` annotation to log resources in a
// user customized way.
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
Name string `json:"name"`
}
// Platform represents a platform to manage. A Platform specifies a [Component]
@@ -278,20 +242,20 @@ type Metadata struct {
// cue export --out yaml ./platform
type Platform struct {
// Kind is a string value representing the resource.
Kind string `json:"kind" yaml:"kind" cue:"\"Platform\""`
Kind string `json:"kind" cue:"\"Platform\""`
// APIVersion represents the versioned schema of this resource.
APIVersion string `json:"apiVersion" yaml:"apiVersion" cue:"string | *\"v1alpha5\""`
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha5\""`
// Metadata represents data about the resource such as the Name.
Metadata Metadata `json:"metadata" yaml:"metadata"`
Metadata Metadata `json:"metadata"`
// Spec represents the platform specification.
Spec PlatformSpec `json:"spec" yaml:"spec"`
Spec PlatformSpec `json:"spec"`
}
// PlatformSpec represents the platform specification.
type PlatformSpec struct {
// Components represents a collection of holos components to manage.
Components []Component `json:"components" yaml:"components"`
Components []Component `json:"components"`
}
// Component represents the complete context necessary to produce a [BuildPlan]
@@ -299,51 +263,17 @@ type PlatformSpec struct {
type Component struct {
// Name represents the name of the component. Injected as the tag variable
// "holos_component_name".
Name string `json:"name" yaml:"name"`
Name string `json:"name"`
// Path represents the path of the component relative to the platform root.
// Injected as the tag variable "holos_component_path".
Path string `json:"path" yaml:"path"`
// Instances represents additional cue instance paths to unify with Path.
// Useful to unify data files into a component BuildPlan. Added in holos
// 0.101.7.
Instances []Instance `json:"instances,omitempty" yaml:"instances,omitempty"`
Path string `json:"path"`
// WriteTo represents the holos render component --write-to flag. If empty,
// the default value for the --write-to flag is used.
WriteTo string `json:"writeTo,omitempty" yaml:"writeTo,omitempty"`
WriteTo string `json:"writeTo,omitempty"`
// Parameters represent user defined input variables to produce various
// [BuildPlan] resources from one component path. Injected as CUE @tag
// variables. Parameters with a "holos_" prefix are reserved for use by the
// Holos Authors. Multiple environments are a prime example of an input
// parameter that should always be user defined, never defined by Holos.
Parameters map[string]string `json:"parameters,omitempty" yaml:"parameters,omitempty"`
// Labels represent selector labels for the component. Copied to the
// resulting BuildPlan.
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
// Annotations represents arbitrary non-identifying metadata. Use the
// `app.holos.run/description` to customize the log message of each BuildPlan.
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
}
// Instance represents a data instance to unify with the configuration.
//
// Useful to unify json and yaml files with cue configuration files for
// integration with other tools. For example, executing holos render platform
// from a pull request workflow after [Kargo] executes the [yaml update] and
// [git wait for pr] promotion steps.
//
// [Kargo]: https://docs.kargo.io/
// [yaml update]: https://docs.kargo.io/references/promotion-steps#yaml-update
// [git wait for pr]: https://docs.kargo.io/references/promotion-steps#git-wait-for-pr
type Instance struct {
// Kind is a discriminator.
Kind string `json:"kind" yaml:"kind" cue:"\"ExtractYAML\""`
// Ignored unless kind is ExtractYAML.
ExtractYAML ExtractYAML `json:"extractYAML,omitempty" yaml:"extractYAML,omitempty"`
}
// ExtractYAML represents a cue data instance encoded as yaml or json. If Path
// refers to a directory all files in the directory are extracted
// non-recursively. Otherwise, path must refer to a file.
type ExtractYAML struct {
Path string `json:"path" yaml:"path"`
Parameters map[string]string `json:"parameters,omitempty"`
}

56
api/v1alpha1/buildplan.go Normal file
View File

@@ -0,0 +1,56 @@
package v1alpha1
import (
"errors"
"fmt"
"strings"
)
// BuildPlan is the primary interface between CUE and the Holos cli.
type BuildPlan struct {
TypeMeta `json:",inline" yaml:",inline"`
// Metadata represents the holos component name
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
Spec BuildPlanSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
}
type BuildPlanSpec struct {
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
Components BuildPlanComponents `json:"components,omitempty" yaml:"components,omitempty"`
// DeployFiles keys represent file paths relative to the cluster deploy
// directory. Map values represent the string encoded file contents. Used to
// write the argocd Application, but may be used to render any file from CUE.
DeployFiles FileContentMap `json:"deployFiles,omitempty" yaml:"deployFiles,omitempty"`
}
type BuildPlanComponents struct {
HelmChartList []HelmChart `json:"helmChartList,omitempty" yaml:"helmChartList,omitempty"`
KubernetesObjectsList []KubernetesObjects `json:"kubernetesObjectsList,omitempty" yaml:"kubernetesObjectsList,omitempty"`
KustomizeBuildList []KustomizeBuild `json:"kustomizeBuildList,omitempty" yaml:"kustomizeBuildList,omitempty"`
Resources map[string]KubernetesObjects `json:"resources,omitempty" yaml:"resources,omitempty"`
}
func (bp *BuildPlan) Validate() error {
errs := make([]string, 0, 2)
if bp.Kind != BuildPlanKind {
errs = append(errs, fmt.Sprintf("kind invalid: want: %s have: %s", BuildPlanKind, bp.Kind))
}
if bp.APIVersion != APIVersion {
errs = append(errs, fmt.Sprintf("apiVersion invalid: want: %s have: %s", APIVersion, bp.APIVersion))
}
if len(errs) > 0 {
return errors.New("invalid BuildPlan: " + strings.Join(errs, ", "))
}
return nil
}
func (bp *BuildPlan) ResultCapacity() (count int) {
if bp == nil {
return 0
}
count = len(bp.Spec.Components.HelmChartList) +
len(bp.Spec.Components.KubernetesObjectsList) +
len(bp.Spec.Components.KustomizeBuildList) +
len(bp.Spec.Components.Resources)
return count
}

30
api/v1alpha1/component.go Normal file
View File

@@ -0,0 +1,30 @@
package v1alpha1
// HolosComponent defines the fields common to all holos component kinds including the Render Result.
type HolosComponent struct {
TypeMeta `json:",inline" yaml:",inline"`
// Metadata represents the holos component name
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
// APIObjectMap holds the marshalled representation of api objects. Think of
// these as resources overlaid at the back of the render pipeline.
APIObjectMap APIObjectMap `json:"apiObjectMap,omitempty" yaml:"apiObjectMap,omitempty"`
// Kustomization holds the marshalled representation of the flux kustomization
// which reconciles resources in git with the api server.
Kustomization `json:",inline" yaml:",inline"`
// Kustomize represents a kubectl kustomize build post-processing step.
Kustomize `json:",inline" yaml:",inline"`
// Skip causes holos to take no action regarding the component.
Skip bool
}
func (hc *HolosComponent) NewResult() *Result {
return &Result{HolosComponent: *hc}
}
func (hc *HolosComponent) GetAPIVersion() string {
return hc.APIVersion
}
func (hc *HolosComponent) GetKind() string {
return hc.Kind
}

11
api/v1alpha1/constants.go Normal file
View File

@@ -0,0 +1,11 @@
package v1alpha1
const (
APIVersion = "holos.run/v1alpha1"
BuildPlanKind = "BuildPlan"
HelmChartKind = "HelmChart"
// ChartDir is the directory name created in the holos component directory to cache a chart.
ChartDir = "vendor"
// ResourcesFile is the file name used to store component output when post-processing with kustomize.
ResourcesFile = "resources.yaml"
)

2
api/v1alpha1/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package v1alpha1 defines the api boundary between CUE and Holos.
package v1alpha1

13
api/v1alpha1/form.go Normal file
View File

@@ -0,0 +1,13 @@
package v1alpha1
import object "github.com/holos-run/holos/service/gen/holos/object/v1alpha1"
// Form represents a collection of Formly json powered form.
type Form struct {
TypeMeta `json:",inline" yaml:",inline"`
Spec FormSpec `json:"spec" yaml:"spec"`
}
type FormSpec struct {
Form object.Form `json:"form" yaml:"form"`
}

184
api/v1alpha1/helm.go Normal file
View File

@@ -0,0 +1,184 @@
package v1alpha1
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/holos-run/holos"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/logger"
"github.com/holos-run/holos/internal/util"
)
// A HelmChart represents a helm command to provide chart values in order to render kubernetes api objects.
type HelmChart struct {
HolosComponent `json:",inline" yaml:",inline"`
// Namespace is the namespace to install into. TODO: Use metadata.namespace instead.
Namespace string `json:"namespace"`
Chart Chart `json:"chart"`
ValuesContent string `json:"valuesContent"`
EnableHooks bool `json:"enableHooks"`
}
type Chart struct {
Name string `json:"name"`
Version string `json:"version"`
Release string `json:"release"`
Repository Repository `json:"repository,omitempty"`
}
type Repository struct {
Name string `json:"name"`
URL string `json:"url"`
}
func (hc *HelmChart) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
result := Result{HolosComponent: hc.HolosComponent}
if err := hc.helm(ctx, &result, path); err != nil {
return nil, err
}
result.addObjectMap(ctx, hc.APIObjectMap)
if err := result.kustomize(ctx); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not kustomize: %w", err))
}
return &result, nil
}
// runHelm provides the values produced by CUE to helm template and returns
// the rendered kubernetes api objects in the result.
func (hc *HelmChart) helm(ctx context.Context, r *Result, path holos.InstancePath) error {
log := logger.FromContext(ctx).With("chart", hc.Chart.Name)
if hc.Chart.Name == "" {
log.WarnContext(ctx, "skipping helm: no chart name specified, use a different component type")
return nil
}
cachedChartPath := filepath.Join(string(path), ChartDir, filepath.Base(hc.Chart.Name))
if isNotExist(cachedChartPath) {
// Add repositories
repo := hc.Chart.Repository
if repo.URL != "" {
out, err := util.RunCmd(ctx, "helm", "repo", "add", repo.Name, repo.URL)
if err != nil {
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
return errors.Wrap(fmt.Errorf("could not run helm repo add: %w", err))
}
// Update repository
out, err = util.RunCmd(ctx, "helm", "repo", "update", repo.Name)
if err != nil {
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
return errors.Wrap(fmt.Errorf("could not run helm repo update: %w", err))
}
} else {
log.DebugContext(ctx, "no chart repository url proceeding assuming oci chart")
}
// Cache the chart
if err := cacheChart(ctx, path, ChartDir, hc.Chart); err != nil {
return fmt.Errorf("could not cache chart: %w", err)
}
}
// Write values file
tempDir, err := os.MkdirTemp("", "holos")
if err != nil {
return errors.Wrap(fmt.Errorf("could not make temp dir: %w", err))
}
defer util.Remove(ctx, tempDir)
valuesPath := filepath.Join(tempDir, "values.yaml")
if err := os.WriteFile(valuesPath, []byte(hc.ValuesContent), 0644); err != nil {
return errors.Wrap(fmt.Errorf("could not write values: %w", err))
}
log.DebugContext(ctx, "helm: wrote values", "path", valuesPath, "bytes", len(hc.ValuesContent))
// Run charts
chart := hc.Chart
args := []string{"template"}
if !hc.EnableHooks {
args = append(args, "--no-hooks")
}
namespace := hc.Namespace
args = append(args, "--include-crds", "--values", valuesPath, "--namespace", namespace, "--kubeconfig", "/dev/null", "--version", chart.Version, chart.Release, cachedChartPath)
helmOut, err := util.RunCmd(ctx, "helm", args...)
if err != nil {
stderr := helmOut.Stderr.String()
lines := strings.Split(stderr, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Error:") {
err = fmt.Errorf("%s: %w", line, err)
}
}
return errors.Wrap(fmt.Errorf("could not run helm template: %w", err))
}
r.accumulatedOutput = helmOut.Stdout.String()
return nil
}
// cacheChart stores a cached copy of Chart in the chart subdirectory of path.
//
// It is assumed that the only method responsible for writing to chartDir is
// cacheChart itself.
//
// This relies on the atomicity of moving temporary directories into place on
// the same filesystem via os.Rename. If a syscall.EEXIST error occurs during
// renaming, it indicates that the cached chart already exists, which is an
// expected scenario when this function is called concurrently.
func cacheChart(ctx context.Context, path holos.InstancePath, chartDir string, chart Chart) error {
log := logger.FromContext(ctx)
cacheTemp, err := os.MkdirTemp(string(path), chartDir)
if err != nil {
return errors.Wrap(fmt.Errorf("could not make temp dir: %w", err))
}
defer util.Remove(ctx, cacheTemp)
chartName := chart.Name
if chart.Repository.Name != "" {
chartName = fmt.Sprintf("%s/%s", chart.Repository.Name, chart.Name)
}
helmOut, err := util.RunCmd(ctx, "helm", "pull", "--destination", cacheTemp, "--untar=true", "--version", chart.Version, chartName)
if err != nil {
return errors.Wrap(fmt.Errorf("could not run helm pull: %w", err))
}
log.Debug("helm pull", "stdout", helmOut.Stdout, "stderr", helmOut.Stderr)
cachePath := filepath.Join(string(path), chartDir)
if err := os.MkdirAll(cachePath, 0777); err != nil {
return errors.Wrap(fmt.Errorf("could not mkdir: %w", err))
}
items, err := os.ReadDir(cacheTemp)
if err != nil {
return errors.Wrap(fmt.Errorf("could not read directory: %w", err))
}
for _, item := range items {
src := filepath.Join(cacheTemp, item.Name())
dst := filepath.Join(cachePath, item.Name())
log.DebugContext(ctx, "rename", "src", src, "dst", dst)
if err := os.Rename(src, dst); err != nil {
var linkErr *os.LinkError
if errors.As(err, &linkErr) && errors.Is(linkErr.Err, syscall.EEXIST) {
log.DebugContext(ctx, "cache already exists", "chart", chart.Name, "chart_version", chart.Version, "path", cachePath)
} else {
return errors.Wrap(fmt.Errorf("could not rename: %w", err))
}
}
}
log.InfoContext(ctx, "cached", "chart", chart.Name, "chart_version", chart.Version, "path", cachePath)
return nil
}
func isNotExist(path string) bool {
_, err := os.Stat(path)
return os.IsNotExist(err)
}

View File

@@ -0,0 +1,21 @@
package v1alpha1
import (
"context"
"github.com/holos-run/holos"
)
const KubernetesObjectsKind = "KubernetesObjects"
// KubernetesObjects represents CUE output which directly provides Kubernetes api objects to holos.
type KubernetesObjects struct {
HolosComponent `json:",inline" yaml:",inline"`
}
// Render produces kubernetes api objects from the APIObjectMap
func (o *KubernetesObjects) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
result := Result{HolosComponent: o.HolosComponent}
result.addObjectMap(ctx, o.APIObjectMap)
return &result, nil
}

View File

@@ -0,0 +1,7 @@
package v1alpha1
// Kustomization holds the rendered flux kustomization api object content for git ops.
type Kustomization struct {
// KsContent is the yaml representation of the flux kustomization for gitops.
KsContent string `json:"ksContent,omitempty" yaml:"ksContent,omitempty"`
}

47
api/v1alpha1/kustomize.go Normal file
View File

@@ -0,0 +1,47 @@
package v1alpha1
import (
"context"
"github.com/holos-run/holos"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/logger"
"github.com/holos-run/holos/internal/util"
)
const KustomizeBuildKind = "KustomizeBuild"
// Kustomize represents resources necessary to execute a kustomize build.
// Intended for at least two use cases:
//
// 1. Process raw yaml file resources in a holos component directory.
// 2. Post process a HelmChart to inject istio, add custom labels, etc...
type Kustomize struct {
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
KustomizeFiles FileContentMap `json:"kustomizeFiles,omitempty" yaml:"kustomizeFiles,omitempty"`
// ResourcesFile is the file name used for api objects in kustomization.yaml
ResourcesFile string `json:"resourcesFile,omitempty" yaml:"resourcesFile,omitempty"`
}
// KustomizeBuild renders plain yaml files in the holos component directory using kubectl kustomize build.
type KustomizeBuild struct {
HolosComponent `json:",inline" yaml:",inline"`
}
// Render produces a Result by executing kubectl kustomize on the holos
// component path. Useful for processing raw yaml files.
func (kb *KustomizeBuild) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
log := logger.FromContext(ctx)
result := Result{HolosComponent: kb.HolosComponent}
// Run kustomize.
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", string(path))
if err != nil {
log.ErrorContext(ctx, kOut.Stderr.String())
return nil, errors.Wrap(err)
}
// Replace the accumulated output
result.accumulatedOutput = kOut.Stdout.String()
// Add CUE based api objects.
result.addObjectMap(ctx, kb.APIObjectMap)
return &result, nil
}

14
api/v1alpha1/objectmap.go Normal file
View File

@@ -0,0 +1,14 @@
package v1alpha1
// Label is an arbitrary unique identifier. Defined as a type for clarity and type checking.
type Label string
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
type Kind string
// APIObjectMap is the shape of marshalled api objects returned from cue to the
// holos cli. A map is used to improve the clarity of error messages from cue.
type APIObjectMap map[Kind]map[Label]string
// FileContentMap is a map of file names to file contents.
type FileContentMap map[string]string

View File

@@ -0,0 +1,15 @@
package v1alpha1
// ObjectMeta represents metadata of a holos component object. The fields are a
// copy of upstream kubernetes api machinery but are by holos objects distinct
// from kubernetes api objects.
type ObjectMeta struct {
// Name uniquely identifies the holos component instance and must be suitable as a file name.
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Namespace confines a holos component to a single namespace via kustomize if set.
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
// Labels are not used but are copied from api machinery ObjectMeta for completeness.
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
// Annotations are not used but are copied from api machinery ObjectMeta for completeness.
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
}

32
api/v1alpha1/platform.go Normal file
View File

@@ -0,0 +1,32 @@
package v1alpha1
import "google.golang.org/protobuf/types/known/structpb"
// 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 {
TypeMeta `json:",inline" yaml:",inline"`
Metadata ObjectMeta `json:"metadata" yaml:"metadata"`
Spec PlatformSpec `json:"spec" yaml:"spec"`
}
// PlatformSpec represents the platform build plan specification.
type PlatformSpec struct {
// Model represents the platform model holos gets from from the
// holos.platform.v1alpha1.PlatformService.GetPlatform method and provides to
// CUE using a tag.
Model structpb.Struct `json:"model" yaml:"model"`
Components []PlatformSpecComponent `json:"components" yaml:"components"`
}
// PlatformSpecComponent represents a component to build or render with flags to
// pass, for example the cluster name.
type PlatformSpecComponent struct {
// Path is the path of the component relative to the platform root.
Path string `json:"path" yaml:"path"`
// Cluster is the cluster name to use when building the component.
Cluster string `json:"cluster" yaml:"cluster"`
}

22
api/v1alpha1/render.go Normal file
View File

@@ -0,0 +1,22 @@
package v1alpha1
import (
"context"
"github.com/holos-run/holos"
)
type Renderer interface {
GetKind() string
Render(ctx context.Context, path holos.InstancePath) (*Result, error)
}
// Render produces a Result representing the kubernetes api objects to
// configure. Each of the various holos component types, e.g. Helm, Kustomize,
// et al, should implement the Renderer interface. This process is best
// conceptualized as a data pipeline, for example a component may render a
// result by first calling helm template, then passing the result through
// kustomize, then mixing in overlay api objects.
func Render(ctx context.Context, r Renderer, path holos.InstancePath) (*Result, error) {
return r.Render(ctx, path)
}

165
api/v1alpha1/result.go Normal file
View File

@@ -0,0 +1,165 @@
package v1alpha1
import (
"context"
"fmt"
"os"
"path/filepath"
"slices"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/logger"
"github.com/holos-run/holos/internal/util"
)
// Result is the build result for display or writing. Holos components Render the Result as a data pipeline.
type Result struct {
HolosComponent
// accumulatedOutput accumulates rendered api objects.
accumulatedOutput string
// DeployFiles keys represent file paths relative to the cluster deploy
// directory. Map values represent the string encoded file contents. Used to
// write the argocd Application, but may be used to render any file from CUE.
DeployFiles FileContentMap `json:"deployFiles,omitempty" yaml:"deployFiles,omitempty"`
}
// Continue returns true if Skip is true indicating the result is to be skipped over.
func (r *Result) Continue() bool {
if r == nil {
return false
}
return r.Skip
}
func (r *Result) Name() string {
return r.Metadata.Name
}
func (r *Result) Filename(writeTo string, cluster string) string {
name := r.Metadata.Name
return filepath.Join(writeTo, "clusters", cluster, "components", name, name+".gen.yaml")
}
func (r *Result) KustomizationFilename(writeTo string, cluster string) string {
return filepath.Join(writeTo, "clusters", cluster, "holos", "components", r.Metadata.Name+"-kustomization.gen.yaml")
}
// AccumulatedOutput returns the accumulated rendered output.
func (r *Result) AccumulatedOutput() string {
return r.accumulatedOutput
}
// addObjectMap renders the provided APIObjectMap into the accumulated output.
func (r *Result) addObjectMap(ctx context.Context, objectMap APIObjectMap) {
log := logger.FromContext(ctx)
b := []byte(r.AccumulatedOutput())
kinds := make([]Kind, 0, len(objectMap))
// Sort the keys
for kind := range objectMap {
kinds = append(kinds, kind)
}
slices.Sort(kinds)
for _, kind := range kinds {
v := objectMap[kind]
// Sort the keys
names := make([]Label, 0, len(v))
for name := range v {
names = append(names, name)
}
slices.Sort(names)
for _, name := range names {
yamlString := v[name]
log.Debug(fmt.Sprintf("%s/%s", kind, name), "kind", kind, "name", name)
b = util.EnsureNewline(b)
header := fmt.Sprintf("---\n# Source: CUE apiObjects.%s.%s\n", kind, name)
b = append(b, []byte(header+yamlString)...)
b = util.EnsureNewline(b)
}
}
r.accumulatedOutput = string(b)
}
// kustomize replaces the accumulated output with the output of kustomize build
func (r *Result) kustomize(ctx context.Context) error {
log := logger.FromContext(ctx)
if r.ResourcesFile == "" {
log.DebugContext(ctx, "skipping kustomize: no resourcesFile")
return nil
}
if len(r.KustomizeFiles) < 1 {
log.DebugContext(ctx, "skipping kustomize: no kustomizeFiles")
return nil
}
tempDir, err := os.MkdirTemp("", "holos.kustomize")
if err != nil {
return errors.Wrap(err)
}
defer util.Remove(ctx, tempDir)
// Write the main api object resources file for kustomize.
target := filepath.Join(tempDir, r.ResourcesFile)
b := []byte(r.AccumulatedOutput())
b = util.EnsureNewline(b)
if err := os.WriteFile(target, b, 0644); err != nil {
return errors.Wrap(fmt.Errorf("could not write resources: %w", err))
}
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
// Write the kustomization tree, kustomization.yaml must be in this map for kustomize to work.
for file, content := range r.KustomizeFiles {
target := filepath.Join(tempDir, file)
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return errors.Wrap(err)
}
b := []byte(content)
b = util.EnsureNewline(b)
if err := os.WriteFile(target, b, 0644); err != nil {
return errors.Wrap(fmt.Errorf("could not write: %w", err))
}
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
}
// Run kustomize.
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", tempDir)
if err != nil {
log.ErrorContext(ctx, kOut.Stderr.String())
return errors.Wrap(err)
}
// Replace the accumulated output
r.accumulatedOutput = kOut.Stdout.String()
return nil
}
func (r *Result) WriteDeployFiles(ctx context.Context, path string) error {
log := logger.FromContext(ctx)
if len(r.DeployFiles) == 0 {
return nil
}
for k, content := range r.DeployFiles {
path := filepath.Join(path, k)
if err := r.Save(ctx, path, content); err != nil {
return errors.Wrap(err)
}
log.InfoContext(ctx, "wrote deploy file", "path", path, "bytes", len(content))
}
return nil
}
// Save writes the content to the filesystem for git ops.
func (r *Result) Save(ctx context.Context, path string, content string) error {
log := logger.FromContext(ctx)
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, os.FileMode(0775)); err != nil {
log.WarnContext(ctx, "could not mkdir", "path", dir, "err", err)
return errors.Wrap(err)
}
// Write the file content
if err := os.WriteFile(path, []byte(content), os.FileMode(0644)); err != nil {
log.WarnContext(ctx, "could not write", "path", path, "err", err)
return errors.Wrap(err)
}
log.DebugContext(ctx, "out: wrote "+path, "action", "write", "path", path, "status", "ok")
return nil
}

20
api/v1alpha1/typemeta.go Normal file
View File

@@ -0,0 +1,20 @@
package v1alpha1
type TypeMeta struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
}
func (tm *TypeMeta) GetKind() string {
return tm.Kind
}
func (tm *TypeMeta) GetAPIVersion() string {
return tm.APIVersion
}
// Discriminator is an interface to discriminate the kind api object.
type Discriminator interface {
GetKind() string
GetAPIVersion() string
}

View File

@@ -1,63 +0,0 @@
package cmd
import (
"context"
"fmt"
"log/slog"
"os"
"runtime/pprof"
"runtime/trace"
"github.com/holos-run/holos/internal/cli"
"github.com/holos-run/holos/internal/holos"
)
// MakeMain makes a main function for the cli or tests.
func MakeMain(options ...holos.Option) func() int {
return func() (exitCode int) {
cfg := holos.New(options...)
slog.SetDefault(cfg.Logger())
ctx := context.Background()
if format := os.Getenv("HOLOS_CPU_PROFILE"); format != "" {
f, _ := os.Create(fmt.Sprintf(format, os.Getppid(), os.Getpid()))
err := pprof.StartCPUProfile(f)
defer func() {
pprof.StopCPUProfile()
f.Close()
}()
if err != nil {
return cli.HandleError(ctx, err, cfg)
}
}
defer memProfile(ctx, cfg)
if format := os.Getenv("HOLOS_TRACE"); format != "" {
f, _ := os.Create(fmt.Sprintf(format, os.Getppid(), os.Getpid()))
err := trace.Start(f)
defer func() {
trace.Stop()
f.Close()
}()
if err != nil {
return cli.HandleError(ctx, err, cfg)
}
}
feature := &holos.EnvFlagger{}
if err := cli.New(cfg, feature).ExecuteContext(ctx); err != nil {
return cli.HandleError(ctx, err, cfg)
}
return 0
}
}
func memProfile(ctx context.Context, cfg *holos.Config) {
if format := os.Getenv("HOLOS_MEM_PROFILE"); format != "" {
f, _ := os.Create(fmt.Sprintf(format, os.Getppid(), os.Getpid()))
defer f.Close()
if err := pprof.WriteHeapProfile(f); err != nil {
_ = cli.HandleError(ctx, err, cfg)
}
}
}

View File

@@ -3,9 +3,9 @@ package main
import (
"os"
"github.com/holos-run/holos/cmd"
"github.com/holos-run/holos/internal/cli"
)
func main() {
os.Exit(cmd.MakeMain()())
os.Exit(cli.MakeMain()())
}

View File

@@ -6,17 +6,20 @@ import (
"testing"
cue "cuelang.org/go/cmd/cue/cmd"
"github.com/holos-run/holos/cmd"
"github.com/holos-run/holos/internal/cli"
"github.com/rogpeppe/go-internal/testscript"
)
func TestMain(m *testing.M) {
os.Exit(testscript.RunMain(m, map[string]func() int{
"holos": cmd.MakeMain(),
"holos": cli.MakeMain(),
"cue": cue.Main,
}))
}
func TestGuides_v1alpha4(t *testing.T) {
testscript.Run(t, params(filepath.Join("v1alpha4", "guides")))
}
func TestGuides_v1alpha5(t *testing.T) {
testscript.Run(t, params(filepath.Join("v1alpha5", "guides")))
}

View File

@@ -1,12 +0,0 @@
# https://github.com/holos-run/holos/issues/358
# holos cue vet should fail verifications with exit code 1
! exec holos cue vet ./policy --path strings.ToLower(kind) ./data/secret.yaml
# holos cue vet should report validation errors to stderr
stderr 'Forbidden. Use an ExternalSecret instead.'
-- data/secret.yaml --
kind: Secret
-- policy/validators.cue --
package policy
secret: kind: "Forbidden. Use an ExternalSecret instead."

File diff suppressed because it is too large Load Diff

View File

@@ -7,11 +7,11 @@ cd $WORK
exec holos generate platform v1alpha5 --force
# Platforms are empty by default.
exec holos render platform
exec holos render platform ./platform
stderr -count=1 '^rendered platform'
# Holos uses CUE to build a platform specification.
exec holos show platform
exec cue export --expression holos --out=yaml ./platform
cmp stdout want/1.platform_spec.yaml
# Define the host and port in projects/blackbox.schema.cue
@@ -22,7 +22,7 @@ mv projects/platform/components/prometheus/prometheus.cue.disabled projects/plat
mv platform/prometheus.cue.disabled platform/prometheus.cue
# Render the platform to render the prometheus chart.
exec holos render platform
exec holos render platform ./platform
stderr -count=1 '^rendered prometheus'
stderr -count=1 '^rendered platform'
cmp deploy/components/prometheus/prometheus.gen.yaml want/1.prometheus.gen.yaml
@@ -73,8 +73,8 @@ core.#BuildPlan & {
metadata: name: _Tags.component.name
}
-- want/1.platform_spec.yaml --
apiVersion: v1alpha5
kind: Platform
apiVersion: v1alpha5
metadata:
name: default
spec:

View File

@@ -0,0 +1,38 @@
# https://github.com/holos-run/holos/issues/332
env HOME=$WORK
# Mock with a stub helm command
env PATH=$WORK/bin:$PATH
chmod 755 bin/helm
# Initialize the platform
exec holos init platform v1alpha5 --force
# when helm update returns an error
! exec holos render platform ./platform
# holos should log the helm error to stderr
stderr 'Error: chart "podinfo" matching 0.0.0 not found in podinfo index'
-- bin/helm --
#! /bin/bash
echo 'Error: chart "podinfo" matching 0.0.0 not found in podinfo index' >&2
exit 2
-- platform/podinfo.cue --
package holos
Platform: Components: podinfo: {
name: "podinfo"
path: "components/podinfo"
}
-- components/podinfo/podinfo.cue --
package holos
// Produce a helm chart build plan.
holos: HelmChart.BuildPlan
HelmChart: #Helm & {
Name: "podinfo"
Chart: {
version: "0.0.0"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
}

View File

@@ -1,422 +0,0 @@
# https://github.com/holos-run/holos/issues/331
# ensure holos show components --labels selects correctly.
# ensure BuildPlan includes labels and annotations from the platform component.
# ensure holos render platform injects the holos_component_labels and
# holos_component_annotations tags.
env HOME=$WORK
exec holos init platform v1alpha5 --force
exec holos show platform
cmp stdout want/platform.yaml
# all buildplans are selected by default
exec holos show buildplans
cmp stdout want/all-buildplans.yaml
# one = works in the selector
exec holos show buildplans --selector app.holos.run/name=empty1-label
cmp stdout want/buildplans.1.yaml
# double == works in the selector
exec holos show buildplans --selector app.holos.run/name==empty2-label
cmp stdout want/buildplans.2.yaml
# not equal != negates the selection
exec holos show buildplans --selector app.holos.run/name!=empty3-label
cmp stdout want/buildplans.3.yaml
exec holos show buildplans --selector app.holos.run/name!=something-else
cmp stdout want/buildplans.4.yaml
-- platform/empty.cue --
package holos
Platform: Components: {
empty1: _
empty2: _
empty3: _
empty4: _
}
-- platform/metadata.cue --
package holos
Platform: Components: [NAME=string]: {
name: NAME
path: "components/empty"
labels: "app.holos.run/name": "\(name)-label"
annotations: "app.holos.run/description": "\(name)-annotation empty test case"
}
-- components/empty/empty.cue --
package holos
Component: #Kubernetes & {}
holos: Component.BuildPlan
-- want/platform.yaml --
apiVersion: v1alpha5
kind: Platform
metadata:
name: default
spec:
components:
- annotations:
app.holos.run/description: empty1-annotation empty test case
labels:
app.holos.run/name: empty1-label
name: empty1
path: components/empty
- annotations:
app.holos.run/description: empty2-annotation empty test case
labels:
app.holos.run/name: empty2-label
name: empty2
path: components/empty
- annotations:
app.holos.run/description: empty3-annotation empty test case
labels:
app.holos.run/name: empty3-label
name: empty3
path: components/empty
- annotations:
app.holos.run/description: empty4-annotation empty test case
labels:
app.holos.run/name: empty4-label
name: empty4
path: components/empty
-- want/empty.yaml --
-- want/all-buildplans.yaml --
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty1
labels:
app.holos.run/name: empty1-label
annotations:
app.holos.run/description: empty1-annotation empty test case
spec:
artifacts:
- artifact: components/empty1/empty1.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty1/empty1.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
---
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty2
labels:
app.holos.run/name: empty2-label
annotations:
app.holos.run/description: empty2-annotation empty test case
spec:
artifacts:
- artifact: components/empty2/empty2.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty2/empty2.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
---
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty3
labels:
app.holos.run/name: empty3-label
annotations:
app.holos.run/description: empty3-annotation empty test case
spec:
artifacts:
- artifact: components/empty3/empty3.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty3/empty3.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
---
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty4
labels:
app.holos.run/name: empty4-label
annotations:
app.holos.run/description: empty4-annotation empty test case
spec:
artifacts:
- artifact: components/empty4/empty4.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty4/empty4.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
-- want/buildplans.1.yaml --
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty1
labels:
app.holos.run/name: empty1-label
annotations:
app.holos.run/description: empty1-annotation empty test case
spec:
artifacts:
- artifact: components/empty1/empty1.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty1/empty1.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
-- want/buildplans.2.yaml --
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty2
labels:
app.holos.run/name: empty2-label
annotations:
app.holos.run/description: empty2-annotation empty test case
spec:
artifacts:
- artifact: components/empty2/empty2.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty2/empty2.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
-- want/buildplans.3.yaml --
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty1
labels:
app.holos.run/name: empty1-label
annotations:
app.holos.run/description: empty1-annotation empty test case
spec:
artifacts:
- artifact: components/empty1/empty1.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty1/empty1.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
---
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty2
labels:
app.holos.run/name: empty2-label
annotations:
app.holos.run/description: empty2-annotation empty test case
spec:
artifacts:
- artifact: components/empty2/empty2.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty2/empty2.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
---
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty4
labels:
app.holos.run/name: empty4-label
annotations:
app.holos.run/description: empty4-annotation empty test case
spec:
artifacts:
- artifact: components/empty4/empty4.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty4/empty4.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
-- want/buildplans.4.yaml --
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty1
labels:
app.holos.run/name: empty1-label
annotations:
app.holos.run/description: empty1-annotation empty test case
spec:
artifacts:
- artifact: components/empty1/empty1.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty1/empty1.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
---
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty2
labels:
app.holos.run/name: empty2-label
annotations:
app.holos.run/description: empty2-annotation empty test case
spec:
artifacts:
- artifact: components/empty2/empty2.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty2/empty2.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
---
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty3
labels:
app.holos.run/name: empty3-label
annotations:
app.holos.run/description: empty3-annotation empty test case
spec:
artifacts:
- artifact: components/empty3/empty3.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty3/empty3.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml
---
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: empty4
labels:
app.holos.run/name: empty4-label
annotations:
app.holos.run/description: empty4-annotation empty test case
spec:
artifacts:
- artifact: components/empty4/empty4.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/empty4/empty4.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources.gen.yaml

View File

@@ -1,64 +0,0 @@
# https://github.com/holos-run/holos/issues/348
# when the optional kustomize patch name field is omitted
exec holos init platform v1alpha5 --force
# want a buildplan shown
exec holos show buildplans
cmp stdout buildplan.yaml
# want this error to go away
! stderr 'cannot convert non-concrete value string'
-- platform/example.cue --
package holos
Platform: Components: example: {
name: "example"
path: "components/example"
}
-- components/example/example.cue --
package holos
import "encoding/yaml"
holos: Component.BuildPlan
Component: #Kustomize & {
KustomizeConfig: Kustomization: patches: [
{
target: kind: "CustomResourceDefinition"
patch: yaml.Marshal([{
op: "add"
path: "/metadata/annotations/example"
value: "example-value"
}])
},
]
}
-- buildplan.yaml --
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: example
spec:
artifacts:
- artifact: components/example/example.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
output: components/example/example.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- patch: |
- op: add
path: /metadata/annotations/example
value: example-value
target:
kind: CustomResourceDefinition
name: ""
resources:
- resources.gen.yaml

View File

@@ -1,50 +0,0 @@
# https://github.com/holos-run/holos/issues/366
# Build tags conditionally include CUE files.
env HOME=$WORK
exec holos init platform v1alpha5 --force
exec holos show platform
cmp stdout want/empty.yaml
exec holos show platform -t foo
cmp stdout want/foo.yaml
-- platform/empty.cue --
@if(foo)
package holos
Platform: Components: foo: _
-- platform/metadata.cue --
package holos
Platform: Components: [NAME=string]: {
name: NAME
path: "components/empty"
labels: "app.holos.run/name": NAME
annotations: "app.holos.run/description": "\(NAME) empty test case"
}
-- components/empty/empty.cue --
package holos
Component: #Kubernetes & {}
holos: Component.BuildPlan
-- want/empty.yaml --
apiVersion: v1alpha5
kind: Platform
metadata:
name: default
spec:
components: []
-- want/foo.yaml --
apiVersion: v1alpha5
kind: Platform
metadata:
name: default
spec:
components:
- annotations:
app.holos.run/description: foo empty test case
labels:
app.holos.run/name: foo
name: foo
path: components/empty

View File

@@ -4,10 +4,10 @@
cd $WORK
# Generate the directory structure we're going to work in.
exec holos init platform v1alpha5 --force
exec holos generate platform v1alpha5 --force
# Platforms are empty by default.
exec holos render platform
exec holos render platform ./platform
stderr -count=1 '^rendered platform'
# When author.#Kubernetes is empty
@@ -31,7 +31,6 @@ spec:
- kind: Resources
output: resources.gen.yaml
resources: {}
validators: []
transformers:
- kind: Kustomize
inputs:
@@ -39,7 +38,15 @@ spec:
output: components/no-name/no-name.gen.yaml
kustomize:
kustomization:
labels:
- includeSelectors: false
pairs: {}
resources:
- resources.gen.yaml
kind: Kustomization
apiVersion: kustomize.config.k8s.io/v1beta1
source:
component:
name: no-name
path: no-path
parameters: {}

View File

@@ -1,37 +0,0 @@
# https://github.com/holos-run/holos/issues/357
exec holos init platform v1alpha5 --force
! exec holos render platform
stderr 'secret.kind: conflicting values "Forbidden. Use an ExternalSecret instead." and "Secret"'
-- validators.cue --
package holos
import "github.com/holos-run/holos/api/author/v1alpha5:author"
#ComponentConfig: author.#ComponentConfig & {
Validators: cue: {
kind: "Command"
command: args: ["holos", "cue", "vet", "./policy", "--path", "strings.ToLower(kind)"]
}
}
-- policy/validations.cue --
package validations
secret: kind: "Forbidden. Use an ExternalSecret instead."
-- platform/example.cue --
package holos
Platform: Components: example: {
name: "example"
path: "components/example"
}
-- components/example/secret.cue --
package holos
holos: Component.BuildPlan
Component: #Kubernetes & {
Resources: Secret: test: {
metadata: name: "test"
}
}

View File

@@ -43,11 +43,6 @@ type ComponentConfig struct {
// Name represents the BuildPlan metadata.name field. Used to construct the
// fully rendered manifest file path.
Name string
// Labels represent the BuildPlan metadata.labels field.
Labels map[string]string
// Annotations represent the BuildPlan metadata.annotations field.
Annotations map[string]string
// Path represents the path to the component producing the BuildPlan.
Path string
// Parameters are useful to reuse a component with various parameters.
@@ -62,10 +57,8 @@ type ComponentConfig struct {
// Resources represents kubernetes resources mixed into the rendered manifest.
Resources core.Resources
// KustomizeConfig represents the kustomize configuration.
// KustomizeConfig represents the configuration kustomize.
KustomizeConfig KustomizeConfig
// Validators represent checks that must pass for output to be written.
Validators map[NameLabel]core.Validator
// Artifacts represents additional artifacts to mix in. Useful for adding
// GitOps resources. Each Artifact is unified without modification into the
// BuildPlan.

View File

@@ -15,21 +15,17 @@ Package core contains schemas for a [Platform](<#Platform>) and [BuildPlan](<#Bu
## Index
- [type Artifact](<#Artifact>)
- [type Auth](<#Auth>)
- [type AuthSource](<#AuthSource>)
- [type BuildPlan](<#BuildPlan>)
- [type BuildPlanSource](<#BuildPlanSource>)
- [type BuildPlanSpec](<#BuildPlanSpec>)
- [type Chart](<#Chart>)
- [type Command](<#Command>)
- [type Component](<#Component>)
- [type ExtractYAML](<#ExtractYAML>)
- [type File](<#File>)
- [type FileContent](<#FileContent>)
- [type FileContentMap](<#FileContentMap>)
- [type FilePath](<#FilePath>)
- [type Generator](<#Generator>)
- [type Helm](<#Helm>)
- [type Instance](<#Instance>)
- [type InternalLabel](<#InternalLabel>)
- [type Join](<#Join>)
- [type Kind](<#Kind>)
@@ -42,7 +38,6 @@ Package core contains schemas for a [Platform](<#Platform>) and [BuildPlan](<#Bu
- [type Resource](<#Resource>)
- [type Resources](<#Resources>)
- [type Transformer](<#Transformer>)
- [type Validator](<#Validator>)
- [type Values](<#Values>)
@@ -61,35 +56,10 @@ Output fields are write\-once. It is an error for multiple Generators or Transfo
```go
type Artifact struct {
Artifact FilePath `json:"artifact,omitempty" yaml:"artifact,omitempty"`
Generators []Generator `json:"generators,omitempty" yaml:"generators,omitempty"`
Transformers []Transformer `json:"transformers,omitempty" yaml:"transformers,omitempty"`
Validators []Validator `json:"validators,omitempty" yaml:"validators,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
```
<a name="Auth"></a>
## type Auth {#Auth}
Auth represents environment variable names containing auth credentials.
```go
type Auth struct {
Username AuthSource `json:"username" yaml:"username"`
Password AuthSource `json:"password" yaml:"password"`
}
```
<a name="AuthSource"></a>
## type AuthSource {#AuthSource}
AuthSource represents a source for the value of an [Auth](<#Auth>) field.
```go
type AuthSource struct {
Value string `json:"value,omitempty" yaml:"value,omitempty"`
FromEnv string `json:"fromEnv,omitempty" yaml:"fromEnv,omitempty"`
Artifact FilePath `json:"artifact,omitempty"`
Generators []Generator `json:"generators,omitempty"`
Transformers []Transformer `json:"transformers,omitempty"`
Skip bool `json:"skip,omitempty"`
}
```
@@ -105,13 +75,27 @@ Holos uses CUE to construct a BuildPlan. A future enhancement will support user
```go
type BuildPlan struct {
// Kind represents the type of the resource.
Kind string `json:"kind" yaml:"kind" cue:"\"BuildPlan\""`
Kind string `json:"kind" cue:"\"BuildPlan\""`
// APIVersion represents the versioned schema of the resource.
APIVersion string `json:"apiVersion" yaml:"apiVersion" cue:"string | *\"v1alpha5\""`
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha5\""`
// Metadata represents data about the resource such as the Name.
Metadata Metadata `json:"metadata" yaml:"metadata"`
Metadata Metadata `json:"metadata"`
// Spec specifies the desired state of the resource.
Spec BuildPlanSpec `json:"spec" yaml:"spec"`
Spec BuildPlanSpec `json:"spec"`
// Source reflects the origin of the BuildPlan.
Source BuildPlanSource `json:"source,omitempty"`
}
```
<a name="BuildPlanSource"></a>
## type BuildPlanSource {#BuildPlanSource}
BuildPlanSource reflects the origin of a [BuildPlan](<#BuildPlan>). Useful to save a build plan to a file, then re\-generate it without needing to process a [Platform](<#Platform>) component collection.
```go
type BuildPlanSource struct {
// Component reflects the component that produced the build plan.
Component Component `json:"component,omitempty"`
}
```
@@ -123,9 +107,9 @@ BuildPlanSpec represents the specification of the [BuildPlan](<#BuildPlan>).
```go
type BuildPlanSpec struct {
// Artifacts represents the artifacts for holos to build.
Artifacts []Artifact `json:"artifacts" yaml:"artifacts"`
Artifacts []Artifact `json:"artifacts"`
// Disabled causes the holos cli to disregard the build plan.
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
Disabled bool `json:"disabled,omitempty"`
}
```
@@ -137,24 +121,13 @@ Chart represents a [Helm](<#Helm>) Chart.
```go
type Chart struct {
// Name represents the chart name.
Name string `json:"name" yaml:"name"`
Name string `json:"name"`
// Version represents the chart version.
Version string `json:"version" yaml:"version"`
Version string `json:"version"`
// Release represents the chart release when executing helm template.
Release string `json:"release" yaml:"release"`
Release string `json:"release"`
// Repository represents the repository to fetch the chart from.
Repository Repository `json:"repository,omitempty" yaml:"repository,omitempty"`
}
```
<a name="Command"></a>
## type Command {#Command}
Command represents a command vetting one or more artifacts. Holos appends fully qualified input file paths to the end of the args list, then executes the command. Inputs are written into a temporary directory prior to executing the command and removed afterwards.
```go
type Command struct {
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
Repository Repository `json:"repository,omitempty"`
}
```
@@ -167,40 +140,19 @@ Component represents the complete context necessary to produce a [BuildPlan](<#B
type Component struct {
// Name represents the name of the component. Injected as the tag variable
// "holos_component_name".
Name string `json:"name" yaml:"name"`
Name string `json:"name"`
// Path represents the path of the component relative to the platform root.
// Injected as the tag variable "holos_component_path".
Path string `json:"path" yaml:"path"`
// Instances represents additional cue instance paths to unify with Path.
// Useful to unify data files into a component BuildPlan. Added in holos
// 0.101.7.
Instances []Instance `json:"instances,omitempty" yaml:"instances,omitempty"`
Path string `json:"path"`
// WriteTo represents the holos render component --write-to flag. If empty,
// the default value for the --write-to flag is used.
WriteTo string `json:"writeTo,omitempty" yaml:"writeTo,omitempty"`
WriteTo string `json:"writeTo,omitempty"`
// Parameters represent user defined input variables to produce various
// [BuildPlan] resources from one component path. Injected as CUE @tag
// variables. Parameters with a "holos_" prefix are reserved for use by the
// Holos Authors. Multiple environments are a prime example of an input
// parameter that should always be user defined, never defined by Holos.
Parameters map[string]string `json:"parameters,omitempty" yaml:"parameters,omitempty"`
// Labels represent selector labels for the component. Copied to the
// resulting BuildPlan.
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
// Annotations represents arbitrary non-identifying metadata. Use the
// `app.holos.run/description` to customize the log message of each BuildPlan.
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
}
```
<a name="ExtractYAML"></a>
## type ExtractYAML {#ExtractYAML}
ExtractYAML represents a cue data instance encoded as yaml or json. If Path refers to a directory all files in the directory are extracted non\-recursively. Otherwise, path must refer to a file.
```go
type ExtractYAML struct {
Path string `json:"path" yaml:"path"`
Parameters map[string]string `json:"parameters,omitempty"`
}
```
@@ -212,7 +164,7 @@ File represents a simple single file copy [Generator](<#Generator>). Useful with
```go
type File struct {
// Source represents a file sub-path relative to the component path.
Source FilePath `json:"source" yaml:"source"`
Source FilePath `json:"source"`
}
```
@@ -257,19 +209,19 @@ Each Generator in an [Artifact](<#Artifact>) must have a distinct Output value f
```go
type Generator struct {
// Kind represents the kind of generator. Must be Resources, Helm, or File.
Kind string `json:"kind" yaml:"kind" cue:"\"Resources\" | \"Helm\" | \"File\""`
Kind string `json:"kind" cue:"\"Resources\" | \"Helm\" | \"File\""`
// Output represents a file for a Transformer or Artifact to consume.
Output FilePath `json:"output" yaml:"output"`
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" yaml:"resources,omitempty"`
Resources Resources `json:"resources,omitempty"`
// Helm generator. Ignored unless kind is Helm.
Helm Helm `json:"helm,omitempty" yaml:"helm,omitempty"`
Helm Helm `json:"helm,omitempty"`
// File generator. Ignored unless kind is File.
File File `json:"file,omitempty" yaml:"file,omitempty"`
File File `json:"file,omitempty"`
}
```
@@ -281,34 +233,18 @@ Helm represents a [Chart](<#Chart>) manifest [Generator](<#Generator>).
```go
type Helm struct {
// Chart represents a helm chart to manage.
Chart Chart `json:"chart" yaml:"chart"`
Chart Chart `json:"chart"`
// Values represents values for holos to marshal into values.yaml when
// rendering the chart.
Values Values `json:"values" yaml:"values"`
Values Values `json:"values"`
// EnableHooks enables helm hooks when executing the `helm template` command.
EnableHooks bool `json:"enableHooks,omitempty" yaml:"enableHooks,omitempty"`
EnableHooks bool `json:"enableHooks,omitempty"`
// Namespace represents the helm namespace flag
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Namespace string `json:"namespace,omitempty"`
// APIVersions represents the helm template --api-versions flag
APIVersions []string `json:"apiVersions,omitempty" yaml:"apiVersions,omitempty"`
APIVersions []string `json:"apiVersions,omitempty"`
// KubeVersion represents the helm template --kube-version flag
KubeVersion string `json:"kubeVersion,omitempty" yaml:"kubeVersion,omitempty"`
}
```
<a name="Instance"></a>
## type Instance {#Instance}
Instance represents a data instance to unify with the configuration.
Useful to unify json and yaml files with cue configuration files for integration with other tools. For example, executing holos render platform from a pull request workflow after [Kargo](<https://docs.kargo.io/>) executes the [yaml update](<https://docs.kargo.io/references/promotion-steps#yaml-update>) and [git wait for pr](<https://docs.kargo.io/references/promotion-steps#git-wait-for-pr>) promotion steps.
```go
type Instance struct {
// Kind is a discriminator.
Kind string `json:"kind" yaml:"kind" cue:"\"ExtractYAML\""`
// Ignored unless kind is ExtractYAML.
ExtractYAML ExtractYAML `json:"extractYAML,omitempty" yaml:"extractYAML,omitempty"`
KubeVersion string `json:"kubeVersion,omitempty"`
}
```
@@ -328,7 +264,7 @@ Join represents a [Transformer](<#Transformer>) using [bytes.Join](<https://pkg.
```go
type Join struct {
Separator string `json:"separator,omitempty" yaml:"separator,omitempty"`
Separator string `json:"separator" cue:"string | *\"---\\n\""`
}
```
@@ -358,9 +294,9 @@ Kustomize represents a kustomization [Transformer](<#Transformer>).
```go
type Kustomize struct {
// Kustomization represents the decoded kustomization.yaml file
Kustomization Kustomization `json:"kustomization" yaml:"kustomization"`
Kustomization Kustomization `json:"kustomization"`
// Files holds file contents for kustomize, e.g. patch files.
Files FileContentMap `json:"files,omitempty" yaml:"files,omitempty"`
Files FileContentMap `json:"files,omitempty"`
}
```
@@ -372,13 +308,7 @@ Metadata represents data about the resource such as the Name.
```go
type Metadata struct {
// Name represents the resource name.
Name string `json:"name" yaml:"name"`
// Labels represents a resource selector.
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
// Annotations represents arbitrary non-identifying metadata. For example
// holos uses the `app.holos.run/description` annotation to log resources in a
// user customized way.
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
Name string `json:"name"`
}
```
@@ -396,14 +326,14 @@ cue export --out yaml ./platform
```go
type Platform struct {
// Kind is a string value representing the resource.
Kind string `json:"kind" yaml:"kind" cue:"\"Platform\""`
Kind string `json:"kind" cue:"\"Platform\""`
// APIVersion represents the versioned schema of this resource.
APIVersion string `json:"apiVersion" yaml:"apiVersion" cue:"string | *\"v1alpha5\""`
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha5\""`
// Metadata represents data about the resource such as the Name.
Metadata Metadata `json:"metadata" yaml:"metadata"`
Metadata Metadata `json:"metadata"`
// Spec represents the platform specification.
Spec PlatformSpec `json:"spec" yaml:"spec"`
Spec PlatformSpec `json:"spec"`
}
```
@@ -415,7 +345,7 @@ PlatformSpec represents the platform specification.
```go
type PlatformSpec struct {
// Components represents a collection of holos components to manage.
Components []Component `json:"components" yaml:"components"`
Components []Component `json:"components"`
}
```
@@ -424,13 +354,10 @@ type PlatformSpec struct {
Repository represents a [Helm](<#Helm>) [Chart](<#Chart>) repository.
The Auth field is useful to configure http basic authentication to the Helm repository. Holos gets the username and password from the environment variables represented by the Auth field.
```go
type Repository struct {
Name string `json:"name" yaml:"name"`
URL string `json:"url" yaml:"url"`
Auth Auth `json:"auth,omitempty" yaml:"auth,omitempty"`
Name string `json:"name"`
URL string `json:"url"`
}
```
@@ -463,33 +390,17 @@ Transformer combines multiple inputs from prior [Generator](<#Generator>) or [Tr
```go
type Transformer struct {
// Kind represents the kind of transformer. Must be Kustomize, or Join.
Kind string `json:"kind" yaml:"kind" cue:"\"Kustomize\" | \"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" yaml:"inputs"`
Inputs []FilePath `json:"inputs"`
// Output represents a file for a subsequent Transformer or Artifact to
// consume.
Output FilePath `json:"output" yaml:"output"`
Output FilePath `json:"output"`
// Kustomize transformer. Ignored unless kind is Kustomize.
Kustomize Kustomize `json:"kustomize,omitempty" yaml:"kustomize,omitempty"`
Kustomize Kustomize `json:"kustomize,omitempty"`
// Join transformer. Ignored unless kind is Join.
Join Join `json:"join,omitempty" yaml:"join,omitempty"`
}
```
<a name="Validator"></a>
## type Validator {#Validator}
Validator validates files. Useful to validate an [Artifact](<#Artifact>) prior to writing it out to the final destination. Holos may execute validators concurrently. See the [validators](<https://holos.run/docs/v1alpha5/tutorial/validators/>) tutorial for an end to end example.
```go
type Validator struct {
// Kind represents the kind of transformer. Must be Kustomize, or Join.
Kind string `json:"kind" yaml:"kind" cue:"\"Command\""`
// Inputs represents the files to validate. Usually the final Artifact.
Inputs []FilePath `json:"inputs" yaml:"inputs"`
// Command represents a validation command. Ignored unless kind is Command.
Command Command `json:"command,omitempty" yaml:"command,omitempty"`
Join Join `json:"join,omitempty"`
}
```

View File

@@ -1,16 +0,0 @@
Integrate the `podinfo` component into the platform.
```bash
cat <<EOF >platform/podinfo.cue
```
```cue showLineNumbers
package holos
Platform: Components: podinfo: {
name: "podinfo"
path: "components/podinfo"
}
```
```bash
EOF
```

View File

@@ -1,34 +0,0 @@
Create a directory for the example `podinfo` component we'll use to render
platform manifests.
```bash
mkdir -p components/podinfo
```
Create the CUE configuration for the example `podinfo` component.
```bash
cat <<EOF >components/podinfo/podinfo.cue
```
```cue showLineNumbers
package holos
holos: Component.BuildPlan
Component: #Helm & {
Name: "podinfo"
Chart: {
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
Values: ui: {
message: string | *"Hello World" @tag(message, type=string)
}
}
```
```bash
EOF
```

View File

@@ -1,7 +1,7 @@
---
description: Architecture diagrams.
slug: architecture
sidebar_position: 100
sidebar_position: 90
---
import RenderPlatformDiagram from '@site/src/diagrams/render-platform-sequence.mdx';

View File

@@ -7,8 +7,6 @@ sidebar_position: 110
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CommonComponent from '../../common/example-component.mdx';
import CommonComponentIntegrate from '../../common/example-component-integrate.mdx';
# ArgoCD Application
@@ -32,10 +30,62 @@ mkdir holos-argocd-application && cd holos-argocd-application
holos init platform v1alpha5
```
### Creating an example Component
### Creating a component
<CommonComponent />
<CommonComponentIntegrate />
Create a directory for the `podinfo` component. Create an empty file and then
add the following CUE configuration to it.
<Tabs groupId="1D2C6013-3D19-4516-8147-5A6EE214CAFF">
<TabItem value="components/podinfo/podinfo.cue" label="Podinfo Helm Chart">
```bash
mkdir -p components/podinfo
touch components/podinfo/podinfo.cue
```
```bash
cat <<EOF >components/podinfo/podinfo.cue
```
```cue showLineNumbers
package holos
holos: Component.BuildPlan
Component: #Helm & {
Name: "podinfo"
Chart: {
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
}
```
```bash
EOF
```
</TabItem>
</Tabs>
Integrate the `podinfo` component into the platform.
<Tabs groupId="tutorial-hello-register-podinfo-component">
<TabItem value="platform/podinfo.cue" label="Register Podinfo">
```bash
cat <<EOF >platform/podinfo.cue
```
```cue showLineNumbers
package holos
Platform: Components: podinfo: {
name: "podinfo"
path: "components/podinfo"
}
```
```bash
EOF
```
</TabItem>
</Tabs>
## Adding ArgoCD Application
@@ -177,7 +227,7 @@ source:
<Tabs groupId="E150C802-7162-4FBF-82A7-77D9ADAEE847">
<TabItem value="command" label="Command">
```bash
holos render platform
holos render platform ./platform
```
</TabItem>
<TabItem value="output" label="Output">
@@ -217,7 +267,8 @@ spec:
[podinfo]: https://github.com/stefanprodan/podinfo
[CUE Module]: https://cuelang.org/docs/reference/modules/
[CUE Tags]: https://cuelang.org/docs/howto/inject-value-into-evaluation-using-tag-attribute/
[Platform]: ../api/author.md#Platform
[Component Parameters]: ../topics/component-parameters.mdx
[Application]: https://argo-cd.readthedocs.io/en/stable/user-guide/application-specification/
[Platform]: ../../api/author.md#Platform
[ComponentConfig]: ../../api/author.md#ComponentConfig
[Artifact]: ../../api/core.md#Artifact
[ComponentConfig]: ../api/author.md#ComponentConfig
[Artifact]: ../api/core.md#Artifact

View File

@@ -1,57 +0,0 @@
---
description: Holos compared to other tools
sidebar_label: Comparison
slug: comparison
sidebar_position: 40
---
{/* cspell:ignore Prodan, rollouts */}
# Holos compared to other tools
## Timoni
Holos and Timoni both aim to solve similar problems but approach them at
different levels of the stack.
Timoni focuses on managing applications by evaluating [CUE] stored in OCI
containers. Its creator, Stephan Prodan, envisions a controller that applies the
resulting manifests. In this process, Timoni defers to [Flux] for managing Helm
charts within the cluster.
In contrast, Holos implements the [Rendered Manifests Pattern] and takes a
different approach, particularly in how it handles [Helm] charts. Like
[ArgoCD], Holos renders Helm charts into manifests using the `helm template`
command in its rendering pipeline. Holos differs from Timoni in several important
ways:
1. **Separation of Responsibilities:** Holos stops short of applying
rendered manifests to a cluster, leaving that task to existing tools like
[ArgoCD], [Flux], or even basic `kubectl apply` commands.
2. **Ecosystem Integration:** By focusing solely on rendering Kubernetes
manifests, Holos creates space for other tools to handle deployment and
management. For instance, Holos integrates seamlessly with [Kargo] for
progressive rollouts, as [Kargo] operates between Holos and the Kubernetes API.
This approach ensures that you're not locked into any specific tool and can
choose the best solution for each task.
3. **Platform Integration:** Holos focuses on integrating multiple Components
into a larger Platform. In Holos terminology, a Component refers to a wrapper
for [Helm] charts, [Kustomize] bases, or raw YAML files, integrated into the
rendering pipeline through [CUE]. A Platform represents the full combination of
these components.
4. **Explicit Rendering Pipeline:** Holos emphasizes flexibility in its
rendering pipeline. The system allows any tool that generates Kubernetes
manifests to be wrapped in a Generator, which can then feed into existing
transformers like [Kustomize]. This explicit separation makes Holos highly
adaptable for different workflows.
[Kargo]: https://kargo.io/
[Flux]: https://fluxcd.io
[Helm]: https://helm.sh
[ArgoCD]: https://argoproj.github.io/cd/
[Kustomize]: https://kustomize.io/
[CUE]: https://cuelang.org/
[Rendered Manifests Pattern]: https://akuity.io/blog/the-rendered-manifests-pattern

View File

@@ -0,0 +1,12 @@
---
description: Re-use components by passing in parameters.
slug: component-parameters
sidebar_position: 400
---
# Component Parameters
Key points:
1. Components can be reused.
2. The Platform spec can pass user defined parameters to a component.

12
doc/md/topics/fleets.mdx Normal file
View File

@@ -0,0 +1,12 @@
---
description: Organize clusters into fleets.
slug: fleets
sidebar_position: 300
---
# Fleets
Key points:
1. Workload fleet.
2. Management fleet.

View File

@@ -1,218 +0,0 @@
---
slug: flux-kustomization
title: Flux Kustomization
description: Configuring a Kustomization for each Component.
sidebar_position: 120
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CommonComponent from '../../common/example-component.mdx';
import CommonComponentIntegrate from '../../common/example-component-integrate.mdx';
# Flux Kustomization
## Overview
This topic covers how to mix in a Flux Kustomization to all components. We'll
use the `Artifacts` field of [ComponentConfig] defined by the author schema.
## The Code
### Generating the structure
Use `holos` to generate a minimal platform directory structure. Start by
creating a blank directory to hold the platform configuration.
```shell
mkdir holos-flux-kustomization && cd holos-flux-kustomization
```
```shell
holos init platform v1alpha5
```
### Creating an example Component
<CommonComponent />
<CommonComponentIntegrate />
## Adding Flux Kustomizations
Configure Holos to render a [Kustomization] by defining an [Artifact] for it in
every BuildPlan holos produces. We're unifying our custom configuration with
the existing `#ComponentConfig` defined in `schema.cue`.
```bash
cat <<EOF >flux-kustomization.cue
```
```cue showLineNumbers
package holos
import (
"path"
flux "kustomize.toolkit.fluxcd.io/kustomization/v1"
)
#ComponentConfig: {
Name: _
OutputBaseDir: _
let ArtifactPath = path.Join([OutputBaseDir, "gitops", "\(Name).kustomization.gen.yaml"], path.Unix)
let ResourcesPath = path.Join(["deploy", OutputBaseDir, "components", Name], path.Unix)
Artifacts: "\(Name)-kustomization": {
artifact: ArtifactPath
generators: [{
kind: "Resources"
output: artifact
resources: Kustomization: (Name): flux.#Kustomization & {
metadata: name: Name
metadata: namespace: "default"
spec: {
interval: "5m"
timeout: "1m"
prune: true
path: ResourcesPath
sourceRef: {
kind: "GitRepository"
name: "webapp"
}
}
}
}]
}
}
```
```bash
EOF
```
## Inspecting the BuildPlan
Our customized `#ComponentConfig` results in the following `BuildPlan`.
:::note
The second artifact around line 40 contains the configured `Kustomization`
resource.
:::
<Tabs groupId="55075C71-02E8-4222-88C0-2D52C82D18FC">
<TabItem value="command" label="Command">
```bash
holos cue export --expression holos --out=yaml ./components/podinfo
```
</TabItem>
<TabItem value="output" label="Output">
```yaml showLineNumbers
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: podinfo
spec:
artifacts:
- artifact: components/podinfo/podinfo.gen.yaml
generators:
- kind: Helm
output: helm.gen.yaml
helm:
chart:
name: podinfo
version: 6.6.2
release: podinfo
repository:
name: podinfo
url: https://stefanprodan.github.io/podinfo
values:
ui:
message: Hello World
enableHooks: false
- kind: Resources
output: resources.gen.yaml
resources: {}
validators: []
transformers:
- kind: Kustomize
inputs:
- helm.gen.yaml
- resources.gen.yaml
output: components/podinfo/podinfo.gen.yaml
kustomize:
kustomization:
resources:
- helm.gen.yaml
- resources.gen.yaml
kind: Kustomization
apiVersion: kustomize.config.k8s.io/v1beta1
- artifact: gitops/podinfo.kustomization.gen.yaml
generators:
- kind: Resources
output: gitops/podinfo.kustomization.gen.yaml
resources:
Kustomization:
podinfo:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: podinfo
namespace: default
spec:
interval: 5m
path: deploy/components/podinfo
prune: true
sourceRef:
kind: GitRepository
name: webapp
timeout: 1m
```
</TabItem>
</Tabs>
## Rendering manifests
<Tabs groupId="E150C802-7162-4FBF-82A7-77D9ADAEE847">
<TabItem value="command" label="Command">
```bash
holos render platform
```
</TabItem>
<TabItem value="output" label="Output">
```
rendered podinfo in 140.341417ms
rendered platform in 140.441333ms
```
</TabItem>
</Tabs>
## Reviewing the Kustomization
The Artifact we added to `#ComponentConfig` will produce a Flux Kustomization
resource for every component in the platform. The output in this example is
located at:
```txt
deploy/gitops/podinfo.kustomization.gen.yaml
```
```yaml showLineNumbers
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: podinfo
namespace: default
spec:
interval: 5m
path: deploy/components/podinfo
prune: true
sourceRef:
kind: GitRepository
name: webapp
timeout: 1m
```
[podinfo]: https://github.com/stefanprodan/podinfo
[CUE Module]: https://cuelang.org/docs/reference/modules/
[CUE Tags]: https://cuelang.org/docs/howto/inject-value-into-evaluation-using-tag-attribute/
[Kustomization]: https://fluxcd.io/flux/components/kustomize/kustomizations/
[Platform]: ../../api/author.md#Platform
[ComponentConfig]: ../../api/author.md#ComponentConfig
[Artifact]: ../../api/core.md#Artifact

View File

@@ -1,19 +0,0 @@
---
slug: .
title: GitOps
description: Managing resources with GitOps.
sidebar_position: 120
---
import DocCardList from '@theme/DocCardList';
# GitOps
This section has self contained articles covering how to manage resources using
GitOps tooling like [ArgoCD] and [Flux].
---
<DocCardList />
[ArgoCD]: https://argo-cd.readthedocs.io/en/stable/
[Flux]: https://fluxcd.io/

View File

@@ -1,20 +0,0 @@
---
description: Kargo
slug: kargo
sidebar_position: 110
---
# Kargo
Holos pairs nicely with [Kargo], offering a holistic solution for code
promotion across stages.
Watch this space for a more detailed write up of the integration being
developed.
If you're interested in this topic, please thumbs up the [Kargo
Topic](https://github.com/holos-run/holos/issues/378) issue, or drop into
[Discord] and let us know about your use case.
[Kargo]: https://kargo.io/
[Discord]: https://discord.gg/JgDVbNpye7

View File

@@ -1,7 +1,7 @@
---
description: Build a local cluster for use with Holos.
slug: local-cluster
sidebar_position: 50
sidebar_position: 100
---
import Tabs from '@theme/Tabs';

View File

@@ -0,0 +1,15 @@
---
description: Management Cluster
slug: management-cluster
sidebar_position: 200
---
# Management Cluster
Key points:
1. Namespaces
2. Certificates
3. Secrets
4. CronJobs
5. GKE autopilot

View File

@@ -1,65 +0,0 @@
---
description: OCI Helm Charts
slug: oci-helm-charts
sidebar_position: 710
---
# OCI Helm Charts
Holos supports OCI Helm charts. Use the following example to get started.
```bash
mkdir -p oci-helm && cd oci-helm
holos init platform v1alpha5
```
```bash
mkdir -p components/podinfo-oci
cat <<EOF > components/podinfo-oci/podinfo-oci.cue
```
```cue showLineNumbers
package holos
holos: Component.BuildPlan
Component: #Helm & {
Chart: {
name: "oci://ghcr.io/stefanprodan/charts/podinfo"
release: "podinfo"
version: "6.6.2"
}
}
```
```bash
EOF
```
Register the component with the platform.
```bash
cat <<EOF >platform/podinfo-oci.cue
```
```cue showLineNumbers
package holos
Platform: Components: podinfo: {
name: "podinfo-oci"
path: "components/podinfo-oci"
}
```
```bash
EOF
```
The OCI chart is cached in the vendor directory and rendered.
```bash
holos render platform
```
```txt
Pulled: ghcr.io/stefanprodan/charts/podinfo:6.6.2
Digest: sha256:83295d47de6d6ca634ed4b952a7572fc176bcc38854d0c11ca0fa197bc5f1154
rendered podinfo-oci in 7.21581325s
rendered platform in 7.216199167s
```

View File

@@ -1,183 +0,0 @@
---
description: Private Helm Repositories
slug: private-helm
sidebar_position: 700
---
# Private Helm
Holos supports private Helm repositories accessed with http basic authentication
since `v0.101.4`. Use the following command to update your author and core
schemas to support this configuration.
```bash
holos init platform v1alpha5 --force
```
## Configuration
Holos uses the Helm SDK and defers to it for authentication to private
repositories. Each Helm Generator supports providing http basic authentication
credentials from environment variables.
For example, the following BuildPlan causes `holos` to get the admin username
password from the `HOLOS_TEST_PASS` environment variable.
```bash
mkdir -p projects/holos/components/private-chart
cat <<EOF > projects/holos/components/private-chart/private-chart.cue
```
```cue showLineNumbers
package holos
holos: Component.BuildPlan
// Test holos can access a private repository with basic auth.
// https://github.com/holos-run/holos/issues/370
Component: #Helm & {
Chart: {
name: "mychart"
version: "0.1.0"
repository: {
name: "holos-test"
url: "https://charts.holos.localhost"
// auth: username: fromEnv: "HOLOS_TEST_USER"
auth: username: value: "admin"
auth: password: fromEnv: "HOLOS_TEST_PASS"
}
}
}
```
```bash
EOF
```
## Verification
Verify `holos` can access a private Helm repository by setting [ChartMuseum] up
on a [Local Cluster]. We'll use https with basic auth to authenticate to the
chart repository.
Using the [bank of holos] repository, deploy chart museum:
```bash
holos render platform -t ChartMuseum
```
Apply the manifests:
```bash
kubectl apply --server-side=true -f deploy/clusters/workload/projects/holos/components/chart-museum
kubectl apply --server-side=true -f deploy/clusters/workload/projects/network/components/httproutes
```
Get the admin password:
```bash
kubectl get secret -n holos chartmuseum-auth -o json \
| jq --exit-status -r '.data.password | @base64d'
```
Add a local repo:
```bash
helm repo add holos-test https://charts.holos.localhost --username admin
```
```txt
Password:
"holos-test" has been added to your repositories
```
:::note
Helm by default stores this password in `~/Library/Preferences/helm/repositories.yaml`
:::
Create a chart:
```bash
helm create mychart
```
```txt
Creating mychart
```
Package it up.
```bash
helm package mychart
```
```txt
Successfully packaged chart and saved it to: mychart-0.1.0.tgz
```
Publish it.
```bash
curl --user "admin:$(pbpaste)" --data-binary "@mychart-0.1.0.tgz" https://charts.holos.localhost/api/charts
```
```json
{"saved":true}
```
Remove all cached charts:
```bash
find . -name vendor | xargs rm -rf
```
Render the chart:
```bash
cat <<EOF > test-private-repo.cue
```
```cue showLineNumbers
@if(TestPrivateRepo)
package holos
// Test holos can access a private repository with basic auth.
// https://github.com/holos-run/holos/issues/370
Projects: holos: #ProjectBuilder & {
team: "holos-authors"
namespaces: holos: _
_components: "private-chart": _
}
```
```bash
EOF
```
```
time holos render platform -t TestPrivateRepo
```
Check the chart was pulled and cached:
```shell
tree ./projects/holos/components/private-chart/vendor
```
```txt
./projects/holos/components/private-chart/vendor
└── 0.1.0
├── mychart
│   ├── Chart.yaml
│   ├── mychart-0.1.0.tgz
│   ├── templates
│   │   ├── NOTES.txt
│   │   ├── _helpers.tpl
│   │   ├── deployment.yaml
│   │   ├── hpa.yaml
│   │   ├── ingress.yaml
│   │   ├── service.yaml
│   │   ├── serviceaccount.yaml
│   │   └── tests
│   │   └── test-connection.yaml
│   └── values.yaml
└── mychart-0.1.0.tgz
6 directories, 11 files
```
[Local Cluster]: ./local-cluster.mdx
[ChartMuseum]: https://chartmuseum.com/docs/
[bank of holos]: https://github.com/holos-run/bank-of-holos

View File

@@ -0,0 +1,12 @@
---
description: Secrets Management
slug: secrets-management
sidebar_position: 150
---
# Secrets Management
Key points:
1. Namespaces
2. ExternalSecrets

View File

@@ -1,425 +0,0 @@
---
slug: clusters
title: Clusters
description: Managing clusters - management and workload sets.
sidebar_position: 100
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CommonComponent from '../../common/example-component.mdx';
# Clusters
## Overview
This topic covers one common method to manage multiple clusters with Holos. We'll
define two schemas to hold cluster attributes. First, a single `#Cluster` then
a `#Clusters` collection. We'll use a `Clusters: #Clusters` struct to look up
configuration data using a key. We'll use the cluster name as the lookup key
identifying the cluster.
We'll also organize sets of similar clusters by defining `#ClusterSet` and
`#ClusterSets`. We'll use a `ClusterSets:
#ClusterSets` struct to configure a management cluster and iterate over all
workload clusters.
## The Code
### Initializing the structure
Use `holos` to generate a minimal platform directory structure. Start by
creating a blank directory to hold the platform configuration.
```shell
mkdir holos-multiple-clusters && cd holos-multiple-clusters
```
```shell
holos init platform v1alpha5
```
### Using an example Component
<CommonComponent />
We'll integrate the component with the platform after we define the
configuration structures.
## Defining Clusters
We'll define a `#Cluster` schema and a `#Clusters` collection in this section.
We'll use these schemas to define a `Clusters` structure we use to manage
multiple clusters.
### Assumptions
We'll make the following assumptions, which hold true for many real world
environments.
1. There are two sets of clusters, workload clusters and management clusters.
2. There is one management cluster.
3. There are multiple workload clusters.
4. Each workload cluster is configured similarly, but not identically, to the
others.
### Prototyping the data
Before we define the schema, let's prototype the data structure we want to work
with. We want a structure that makes it easy to iterate over each cluster in
two distinct sets of clusters, management clusters and workload clusters. The
following `ClusterSets` struct accomplishes this goal.
```yaml showLineNumbers
management:
name: management
clusters:
management:
name: management
region: us-central1
set: management
workload:
name: workload
clusters:
e1:
name: e1
region: us-east1
set: workload
w1:
name: w1
region: us-west1
set: workload
```
:::tip
The `ClusterSets` data structure supports iterating over each cluster in each
cluster set.
:::
:::important
You're free to define your own fields and structures like we define `region` in
this topic.
:::
### Defining the schema
Armed with a concrete example of the structure, we can write a schema to define
and validate the data.
In CUE, schema definitions are usually defined at the root so they're accessible
in all subdirectories. The following is one example schema, you're free to
modify it to your situation. Holos is flexible, supporting schemas that match
your unique use case.
```bash
cat <<EOF > clusters.schema.cue
```
```cue showLineNumbers
package holos
import "strings"
// #Cluster represents one cluster
#Cluster: {
// name represents the cluster name.
name: string & =~"[a-z][a-z0-9]+" & strings.MinRunes(2) & strings.MaxRunes(63)
// Constrain the regions. No default, the region must be specified.
region: "us-east1" | "us-central1" | "us-west1"
// Each cluster must be in only one set of clusters. All but one cluster are
// workload clusters, so make it the default.
set: "management" | *"workload"
}
// #Clusters represents a cluster collection structure
#Clusters: {
// name is the lookup key for the collection.
[NAME=string]: #Cluster & {
// name must match the struct field name.
name: NAME
}
}
// #ClusterSet represents a set of clusters.
#ClusterSet: {
// name represents the cluster set name.
name: string & =~"[a-z][a-z0-9]+" & strings.MinRunes(2) & strings.MaxRunes(63)
clusters: #Clusters & {
// Constrain the cluster set to clusters having the same set. Ensures
// clusters are never mis-categorized.
[_]: set: name
}
}
// #ClusterSets represents a cluster set collection.
#ClusterSets: {
// name is the lookup key for the collection.
[NAME=string]: #ClusterSet & {
// name must match the struct field name.
name: NAME
}
}
```
```bash
EOF
```
### Defining the data
With a schema defined, we also define the data close to the root so it's
accessible through the unified configuration tree.
```bash
cat <<EOF > clusters.cue
```
```cue showLineNumbers
package holos
Clusters: #Clusters & {
// Management Cluster
management: region: "us-central1"
management: set: "management"
// Local Cluster
local: region: "us-west1"
// Some example clusters. Add new clusters to the Clusters struct like this.
e1: region: "us-east1"
e2: region: "us-east1"
e3: region: "us-east1"
w1: region: "us-west1"
w2: region: "us-west1"
w3: region: "us-west1"
}
// ClusterSets is dynamically built from the Clusters structure.
ClusterSets: #ClusterSets & {
// Map every cluster into the correct set.
for CLUSTER in Clusters {
(CLUSTER.set): clusters: (CLUSTER.name): CLUSTER
}
}
```
```bash
EOF
```
### Inspecting the data
We'll use the `holos cue` command to inspect the `ClusterSets` data structure we
just defined.
<Tabs groupId="9190BDAD-B4C5-4386-9C94-8E178AA6178A">
<TabItem value="command" label="Command">
```bash
holos cue export --expression ClusterSets --out=yaml ./
```
</TabItem>
<TabItem value="output" label="Output">
```yaml showLineNumbers
management:
name: management
clusters:
management:
name: management
region: us-central1
set: management
workload:
name: workload
clusters:
local:
name: local
region: us-west1
set: workload
e1:
name: e1
region: us-east1
set: workload
e2:
name: e2
region: us-east1
set: workload
e3:
name: e3
region: us-east1
set: workload
w1:
name: w1
region: us-west1
set: workload
w2:
name: w2
region: us-west1
set: workload
w3:
name: w3
region: us-west1
set: workload
```
</TabItem>
</Tabs>
This looks like our prototype, we're confident we can iterate over each cluster
in each set.
## Integrating Components
The `ClusterSets` data structure unlocks the capability to iterate over each
cluster in each cluster set. We'll use this capability to integrate the
`podinfo` component with each cluster in the platform.
### Configuring the Output directory
We need to configure `holos` to write output manifests into a cluster specific
output directory. We'll use the [ComponentConfig] `OutputBaseDir` field for
this purpose. We'll pass the value of this field as a component parameter.
```bash
cat <<EOF > componentconfig.cue
```
```cue showLineNumbers
package holos
#ComponentConfig: {
// Inject the output base directory from platform component parameters.
OutputBaseDir: string @tag(outputBaseDir, type=string)
}
```
```bash
EOF
```
### Integrating Podinfo
```bash
cat <<EOF >platform/podinfo.cue
```
```cue showLineNumbers
package holos
// Manage podinfo on all workload clusters.
for CLUSTER in ClusterSets.workload.clusters {
// We use the cluster name to disambiguate different podinfo build plans.
Platform: Components: "\(CLUSTER.name)-podinfo": {
name: "podinfo"
// Reuse the same component across multiple workload clusters.
path: "components/podinfo"
// Configure a cluster-unique message in the podinfo UI.
parameters: message: "Hello, I am cluster \(CLUSTER.name) in region \(CLUSTER.region)"
// Write to deploy/{outputBaseDir}/components/{name}/{name}.gen.yaml
parameters: outputBaseDir: "clusters/\(CLUSTER.name)"
}
}
```
```bash
EOF
```
## Rendering manifests
### Rendering the Platform
Render the platform to configure `podinfo` on each cluster.
<Tabs groupId="34A2D80B-0E86-4142-B65B-7DF70C47E1D2">
<TabItem value="command" label="Command">
```bash
holos render platform
```
</TabItem>
<TabItem value="output" label="Output">
```txt
cached podinfo 6.6.2
rendered podinfo in 164.278583ms
rendered podinfo in 165.48525ms
rendered podinfo in 165.186208ms
rendered podinfo in 165.831792ms
rendered podinfo in 166.845208ms
rendered podinfo in 167.000208ms
rendered podinfo in 167.012208ms
rendered platform in 167.06525ms
```
</TabItem>
</Tabs>
### Inspecting the Tree
Rendering the platform produces the following rendered manifests.
```bash
tree deploy
```
```txt showLineNumbers
deploy
└── clusters
├── e1
│   └── components
│   └── podinfo
│   └── podinfo.gen.yaml
├── e2
│   └── components
│   └── podinfo
│   └── podinfo.gen.yaml
├── e3
│   └── components
│   └── podinfo
│   └── podinfo.gen.yaml
├── local
│   └── components
│   └── podinfo
│   └── podinfo.gen.yaml
├── w1
│   └── components
│   └── podinfo
│   └── podinfo.gen.yaml
├── w2
│   └── components
│   └── podinfo
│   └── podinfo.gen.yaml
└── w3
└── components
└── podinfo
└── podinfo.gen.yaml
23 directories, 7 files
```
### Inspecting the Variation
Note how each component has slight variation using the component parameters.
```bash
diff -U2 deploy/clusters/{e,w}1/components/podinfo/podinfo.gen.yaml
```
```diff
--- deploy/clusters/e1/components/podinfo/podinfo.gen.yaml 2024-11-17 14:20:17
+++ deploy/clusters/w1/components/podinfo/podinfo.gen.yaml 2024-11-17 14:20:17
@@ -61,5 +61,5 @@
env:
- name: PODINFO_UI_MESSAGE
- value: Hello, I am cluster e1 in region us-east1
+ value: Hello, I am cluster w1 in region us-west1
- name: PODINFO_UI_COLOR
value: '#34577c'
```
## Concluding Remarks
In this topic we covered how to use CUE structures to organize multiple clusters
into various sets.
1. Clusters are defined in one place at the root of the configuration.
2. Clusters may be organized into sets by their purpose.
3. Most organizations have at least two sets, a set of workload clusters and a
set of management clusters.
4. Holos uses CUE, a super set of JSON. New clusters may be added by dropping a
JSON file into the root of the repository.
5. The pattern of defining a `#Cluster` and a `#Clusters` collection is a
general pattern. We'll see the same pattern for environments, projects, owners,
and more.
6. Component parameters are a flexible way to inject user defined configuration
from the platform level into a reusable component.
[ClusterSet]: https://multicluster.sigs.k8s.io/api-types/cluster-set/
[Environments]: ./environments.mdx
[Namespace Sameness - SIG Multicluster Position Statement]: https://github.com/kubernetes/community/blob/master/sig-multicluster/namespace-sameness-position-statement.md
[ComponentConfig]: ../../api/author.md#ComponentConfig

View File

@@ -1,521 +0,0 @@
---
slug: environments
title: Environments
description: Managing Environments - dev, test, stage, prod.
sidebar_position: 130
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CommonComponent from '../../common/example-component.mdx';
# Environments
## Overview
This topic covers how to model environments in Holos. We'll define schemas for
`#Environment` and `#Environments` to represent one environment and a
collection. The `Environments: #Environments` struct maps environment names to
configurations.
:::note
This approach unifies the component definition with the overall platform
configuration, creating a tight coupling between the two.
:::
This tight coupling is appropriate when you're configuring your own platform.
For example:
1. When you're integrating third party software into your own platform.
2. When you're configuring first party in-house software into your own platform.
This approach is not well suited to writing a component to share outside of your
own organization, which we can think of as configuring someone else's platform.
## The Code
### Generating the structure
Use `holos init platform` to generate a minimal platform structure:
```shell
mkdir holos-environments-tutorial && cd holos-environments-tutorial
holos init platform v1alpha5
```
### Using an example Component
Create a directory for the example `podinfo` component we'll use to render
platform manifests.
```bash
mkdir -p components/podinfo
```
Create the CUE configuration for the example `podinfo` component.
```bash
cat <<EOF >components/podinfo/podinfo.cue
```
```cue showLineNumbers
package holos
holos: Component.BuildPlan
Component: #Helm & {
Chart: {
name: "podinfo"
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
Values: ui: {
message: string | *"Hello World" @tag(message, type=string)
}
}
```
```bash
EOF
```
We'll integrate the component with the platform after we define the
configuration structures.
## Defining Environments
We'll define an `#Environment` schema `#Environments` collection. We'll use
these schemas to define an `Environments` struct of concrete configuration
values.
### Assumptions
There are two tiers of environments, prod and nonprod. Prod environments
organized along broad jurisdictions, for example US and EU. Nonprod
environments are organized by purpose, dev, test, and stage.
### Prototyping the data
Before we define the schema, let's prototype the data structure we want to work
with from the perspective of each component.
Let's imagine we're configuring `podinfo` to comply with regulations. When
podinfo is deployed to production in the EU, we'll configure opt-in behavior.
In the US we'll configure opt-out behavior.
We'll pass the environment name as a component parameter. The component
definition can then look up the jurisdiction to determine the appropriate
configuration values.
```shell
holos cue export --out=yaml --expression Environments
```
```yaml showLineNumbers
prod-pdx:
name: prod-pdx
tier: prod
jurisdiction: us
state: oregon
prod-cmh:
name: prod-cmh
tier: prod
jurisdiction: us
state: ohio
prod-ams:
name: prod-ams
tier: prod
jurisdiction: eu
state: netherlands
dev:
name: dev
tier: nonprod
jurisdiction: us
state: oregon
test:
name: test
tier: nonprod
jurisdiction: us
state: oregon
stage:
name: stage
tier: nonprod
jurisdiction: us
state: oregon
```
### Defining the schema
Given the example structure, we can write a schema to define and validate the
data.
```shell
cat <<EOF > environments.schema.cue
```
```cue showLineNumbers
package holos
#Environment: {
name: string
tier: "prod" | "nonprod"
jurisdiction: "us" | "eu" | "uk" | "global"
state: "oregon" | "ohio" | "germany" | "netherlands" | "england" | "global"
// Prod environment names must be prefixed with prod for clarity.
if tier == "prod" {
name: "prod" | =~"^prod-"
}
}
#Environments: {
[NAME=string]: #Environment & {
name: NAME
}
}
```
```shell
EOF
```
### Adding configuration
With a schema defined, we can fill in the concrete values.
```shell
cat <<EOF > environments.cue
```
```cue showLineNumbers
package holos
// Injected from Platform.spec.components.parameters.EnvironmentName
EnvironmentName: string @tag(EnvironmentName)
Environments: #Environments & {
"prod-pdx": {
tier: "prod"
jurisdiction: "us"
state: "oregon"
}
"prod-cmh": {
tier: "prod"
jurisdiction: "us"
state: "ohio"
}
"prod-ams": {
tier: "prod"
jurisdiction: "eu"
state: "netherlands"
}
// Nonprod environments are colocated together.
_nonprod: {
tier: "nonprod"
jurisdiction: "us"
state: "oregon"
}
dev: _nonprod
test: _nonprod
stage: _nonprod
}
```
```shell
EOF
```
### Inspecting the configuration
Inspect the `Environments` data structure to verify the schema and concrete
values are what we want.
<Tabs groupId="FF820F5A-A85F-464D-B299-39CAAFFCE5C6">
<TabItem value="command" label="Command">
```bash
holos cue export --out=yaml --expression Environments
```
</TabItem>
<TabItem value="output" label="Output">
```yaml showLineNumbers
prod-pdx:
name: prod-pdx
tier: prod
jurisdiction: us
state: oregon
prod-cmh:
name: prod-cmh
tier: prod
jurisdiction: us
state: ohio
prod-ams:
name: prod-ams
tier: prod
jurisdiction: eu
state: netherlands
dev:
name: dev
tier: nonprod
jurisdiction: us
state: oregon
test:
name: test
tier: nonprod
jurisdiction: us
state: oregon
stage:
name: stage
tier: nonprod
jurisdiction: us
state: oregon
```
</TabItem>
</Tabs>
This looks like our prototype, we're confident we can iterate over each
environment and get a handle on the configuration values we need.
## Integrating components
The `Environments` data structure unlocks the capability to look up concrete
values specific to a named environment. We'll use this capability to configure
the `podinfo` component in compliance with the regulations of the jurisdiction.
### Configuring the environment
Inject the environment name when we integrate `podinfo` with the platform.
```shell
cat <<EOF > platform/podinfo.cue
```
```cue showLineNumbers
package holos
Platform: Components: {
podinfoPDX: ProdPodinfo & {_city: "pdx"}
podinfoCMH: ProdPodinfo & {_city: "cmh"}
podinfoAMS: ProdPodinfo & {_city: "ams"}
podinfoDEV: {
name: "podinfo-dev"
path: "components/podinfo"
labels: "app.holos.run/component": "podinfo"
parameters: EnvironmentName: "dev"
}
}
let ProdPodinfo = {
_city: string
name: "podinfo-\(_city)"
path: "components/podinfo"
labels: "app.holos.run/component": "podinfo"
labels: "app.holos.run/tier": "prod"
labels: "app.holos.run/city": _city
parameters: EnvironmentName: "prod-\(_city)"
}
```
```
EOF
```
### Using the environment
Now we can configure `podinfo` based on the jurisdiction of the environment.
```shell
cat <<EOF > components/podinfo/cookie-consent.cue
```
```cue showLineNumbers
package holos
// Schema definition for our configuration.
#Values: {
ui: enableCookieConsent: *true | false
ui: message: string
}
// Map jurisdiction to helm values
JurisdictionValues: {
// Enable cookie consent by default in any jurisdiction.
[_]: #Values
// Disable in the US.
us: ui: enableCookieConsent: false
eu: ui: enableCookieConsent: true
}
// Look up the configuration values associated with the environment name.
Component: Values: JurisdictionValues[Environments[EnvironmentName].jurisdiction]
```
```shell
EOF
```
### Inspecting the BuildPlans
With the above configuration, we can inspect the buildplans for this component.
The prod environment in Amsterdam has cookie consent enabled on line 26.
<Tabs groupId="6EC991F3-F78C-43F1-8A6D-E68D8BDAF58B">
<TabItem value="command" label="Command">
```bash
holos show buildplans --selector app.holos.run/city=ams
```
</TabItem>
<TabItem value="output" label="Output">
```yaml showLineNumbers
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: podinfo-ams
labels:
app.holos.run/city: ams
app.holos.run/component: podinfo
app.holos.run/name: podinfo-ams
app.holos.run/tier: prod
spec:
artifacts:
- artifact: components/podinfo-ams/podinfo-ams.gen.yaml
generators:
- kind: Helm
output: helm.gen.yaml
helm:
chart:
name: podinfo
version: 6.6.2
release: podinfo
repository:
name: podinfo
url: https://stefanprodan.github.io/podinfo
values:
ui:
# highlight-next-line
enableCookieConsent: true
message: Hello World
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- helm.gen.yaml
- resources.gen.yaml
output: components/podinfo-ams/podinfo-ams.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
labels:
- includeSelectors: false
pairs: {}
resources:
- helm.gen.yaml
- resources.gen.yaml
- artifact: gitops/podinfo-ams.application.gen.yaml
generators:
- kind: Resources
output: gitops/podinfo-ams.application.gen.yaml
resources:
Application:
podinfo-ams:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: podinfo-ams
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
project: default
source:
path: deploy/components/podinfo-ams
repoURL: https://github.com/brenix/holos-demo.git
targetRevision: main
```
</TabItem>
</Tabs>
In Portland cookie consent is disabled.
<Tabs groupId="3438335B-1FFC-4838-B8DE-C54B8346CDB4">
<TabItem value="command" label="Command">
```bash
holos show buildplans --selector app.holos.run/city=pdx
```
</TabItem>
<TabItem value="output" label="Output">
```yaml showLineNumbers
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: podinfo-pdx
labels:
app.holos.run/city: pdx
app.holos.run/component: podinfo
app.holos.run/name: podinfo-pdx
app.holos.run/tier: prod
spec:
artifacts:
- artifact: components/podinfo-pdx/podinfo-pdx.gen.yaml
generators:
- kind: Helm
output: helm.gen.yaml
helm:
chart:
name: podinfo
version: 6.6.2
release: podinfo
repository:
name: podinfo
url: https://stefanprodan.github.io/podinfo
values:
ui:
# highlight-next-line
enableCookieConsent: false
message: Hello World
- kind: Resources
output: resources.gen.yaml
transformers:
- kind: Kustomize
inputs:
- helm.gen.yaml
- resources.gen.yaml
output: components/podinfo-pdx/podinfo-pdx.gen.yaml
kustomize:
kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
labels:
- includeSelectors: false
pairs: {}
resources:
- helm.gen.yaml
- resources.gen.yaml
- artifact: gitops/podinfo-pdx.application.gen.yaml
generators:
- kind: Resources
output: gitops/podinfo-pdx.application.gen.yaml
resources:
Application:
podinfo-pdx:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: podinfo-pdx
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
project: default
source:
path: deploy/components/podinfo-pdx
repoURL: https://github.com/brenix/holos-demo.git
targetRevision: main
```
</TabItem>
</Tabs>
## Concluding Remarks
In this topic we covered how to use a CUE structure to define attributes of prod
and nonprod environments.
1. We passed the environment name as a parameter to each component using a CUE `@tag`.
2. The component definition uses the environment name as a key to get a handle
on attributes. For example, the jurisdiction a service operates within.
3. The example podinfo component uses an additional structure to map
jurisdictions to concrete configuration values.

View File

@@ -1,25 +0,0 @@
---
slug: .
title: Structures
description: Commonly used CUE structures.
sidebar_position: 120
---
import DocCardList from '@theme/DocCardList';
# Structures
This section has self contained articles covering commonly used CUE structures.
These structures are organized and presented as recipes you may adopt and adjust
to your unique organization.
:::important
Structures are defined by Holos Users, unlike the standardized [Core] and
[Author] schemas defined by the Holos Authors.
:::
---
<DocCardList />
[Core]: ../../api/core.md
[Author]: ../../api/author.md

View File

@@ -0,0 +1,12 @@
---
description: Workload Cluster
slug: workload-cluster
sidebar_position: 250
---
# Workload Cluster
Key points:
1. Namespaces
2. ExternalSecrets

View File

@@ -1,7 +0,0 @@
exec bash -c 'bash -euo pipefail $WORK/command.sh 2>&1'
cmp stdout $WORK/output.txt
-- command.sh --
holos --version
-- output.txt --
0.102.5

View File

@@ -1,374 +0,0 @@
# Set $HOME because:
# - Helm uses it for temporary files
# - Git requires it for setting author name/email globally
env HOME=$WORK/.tmp
chmod 0755 $WORK/update.sh
# Configure git author for testscript execution
exec git config --global user.name 'Holos Docs'
exec git config --global user.email 'hello@holos.run'
exec git config --global init.defaultBranch main
# Remove the tutorial directory if it already exists
exec rm -rf holos-helm-values-tutorial
# Create and change to the tutorial directory, and then initialize the Holos platform
exec bash -c 'bash -euo pipefail mkdir-and-init.sh'
cd holos-helm-values-tutorial
# Git init and create the component directories
exec bash -c 'bash -euo pipefail $WORK/git-init.sh'
exec bash -c 'bash -euo pipefail $WORK/mkdir-components.sh'
# Combine and execute the multiline prometheus/blackbox component header/body/trailer files
exec cat $WORK/prometheus-component-header.sh ../prometheus-component-body.cue ../eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
exec cat $WORK/blackbox-component-header.sh ../blackbox-component-body.cue ../eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Combine and execute the multiline platform registration header/body/trailer files.
exec cat $WORK/register-components-header.sh ../register-components-body.cue ../eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Render the platform, capture stdout, and use update.sh to gate whether the
# output file should be updated.
#
# NOTE: The [net] condition will test whether external network access is available
[net] exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
[net] stdin stdout
exec $WORK/update.sh $WORK/register-components-output.txt
# Commit and conditionally update the output file
exec bash -c 'bash -euo pipefail $WORK/register-components-git-commit.sh'
stdin stdout
exec $WORK/update.sh $WORK/register-components-git-commit-output.txt
# Import values
exec bash -c 'bash -euo pipefail $WORK/import-prometheus-values.sh'
exec bash -c 'bash -euo pipefail $WORK/import-blackbox-values.sh'
# Render, update the output file, commit, and update the commit output file.
[net] exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
[net] stdin stdout
exec $WORK/update.sh $WORK/import-values-render-output.txt
exec bash -c 'bash -euo pipefail $WORK/import-values-git-commit.sh'
stdin stdout
exec $WORK/update.sh $WORK/import-values-git-output.txt
# Create the common configuration path
exec bash -c 'bash -euo pipefail $WORK/mkdir-common-config.sh'
# Combine and execute the common configuration header/body/trailer to write the cue file.
exec cat $WORK/blackbox-common-config-header.sh ../blackbox-common-config-body.cue ../eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Git commit blackbox common config
exec bash -c 'bash -euo pipefail $WORK/blackbox-common-config-git-commit.sh'
stdin stdout
exec $WORK/update.sh $WORK/blackbox-common-config-git-output.txt
# Patch the common config values file and write to output file.
#
# NOTE: Using a symlink here because the patch script references values.patch
# within the same directory, but it actually lives one directory up in the
# testscript $WORK dir.
exec ln -s $WORK/values.patch values.patch
exec bash -c 'bash -euo pipefail $WORK/common-config-patch.sh'
stdin stdout
exec $WORK/update.sh $WORK/common-config-patch.txt
# Remove patch and commit changes
exec bash -c 'bash -euo pipefail $WORK/common-config-rm.sh'
exec bash -c 'bash -euo pipefail $WORK/common-config-git.sh'
stdin stdout
exec $WORK/update.sh $WORK/common-config-git-output.txt
# Final render and update of output file.
[net] exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
[net] stdin stdout
exec $WORK/update.sh $WORK/reviewing-changes-git-output.txt
# Git diff and write to output file.
exec bash -c 'bash -euo pipefail $WORK/git-diff.sh'
stdin stdout
exec $WORK/update.sh $WORK/git.diff
# Final commit and write to output file
exec bash -c 'bash -euo pipefail $WORK/reviewing-changes-git-commit.sh'
stdin stdout
exec $WORK/update.sh $WORK/reviewing-changes-git-output.txt
# Clean up the tutorial directory and tmp $HOME directory
cd $WORK
exec rm -rf holos-helm-values-tutorial
exec rm -rf $HOME
-- update.sh --
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"
-- mkdir-and-init.sh --
mkdir holos-helm-values-tutorial
cd holos-helm-values-tutorial
holos init platform v1alpha5
-- git-init.sh --
git init . && git add . && git commit -m "initial commit"
-- mkdir-components.sh --
mkdir -p components/prometheus components/blackbox
-- prometheus-component-header.sh --
cat <<EOF > components/prometheus/prometheus.cue
-- prometheus-component-body.cue --
package holos
// Produce a helm chart build plan.
holos: Helm.BuildPlan
Helm: #Helm & {
Chart: {
name: "prometheus"
version: "25.27.0"
repository: {
name: "prometheus-community"
url: "https://prometheus-community.github.io/helm-charts"
}
}
}
-- eof-trailer.sh --
EOF
-- blackbox-component-header.sh --
cat <<EOF > components/blackbox/blackbox.cue
-- blackbox-component-body.cue --
package holos
// Produce a helm chart build plan.
holos: Helm.BuildPlan
Helm: #Helm & {
Chart: {
name: "prometheus-blackbox-exporter"
version: "9.0.1"
repository: {
name: "prometheus-community"
url: "https://prometheus-community.github.io/helm-charts"
}
}
}
-- register-components-header.sh --
cat <<EOF > platform/prometheus.cue
-- register-components-body.cue --
package holos
Platform: Components: {
prometheus: {
name: "prometheus"
path: "components/prometheus"
}
blackbox: {
name: "blackbox"
path: "components/blackbox"
}
}
-- render.sh --
holos render platform
-- register-components-output.txt --
cached prometheus-blackbox-exporter 9.0.1
rendered blackbox in 3.825430417s
cached prometheus 25.27.0
rendered prometheus in 4.840089667s
rendered platform in 4.840137792s
-- register-components-git-commit.sh --
git add . && git commit -m 'add blackbox and prometheus'
-- register-components-git-commit-output.txt --
[main b5df111] add blackbox and prometheus
5 files changed, 1550 insertions(+)
create mode 100644 components/blackbox/blackbox.cue
create mode 100644 components/prometheus/prometheus.cue
create mode 100644 deploy/components/blackbox/blackbox.gen.yaml
create mode 100644 deploy/components/prometheus/prometheus.gen.yaml
create mode 100644 platform/prometheus.cue
-- import-prometheus-values.sh --
holos cue import \
--package holos \
--path 'Helm: Values:' \
--outfile components/prometheus/values.cue \
components/prometheus/vendor/25.27.0/prometheus/values.yaml
-- import-blackbox-values.sh --
holos cue import \
--package holos \
--path 'Helm: Values:' \
--outfile components/blackbox/values.cue \
components/blackbox/vendor/9.0.1/prometheus-blackbox-exporter/values.yaml
-- import-values-render-output.txt --
rendered blackbox in 365.936792ms
rendered prometheus in 371.855875ms
rendered platform in 372.109916ms
-- import-values-git-commit.sh --
git add . && git commit -m 'import values'
-- import-values-git-output.txt --
[main 52e90ea] import values
2 files changed, 1815 insertions(+)
create mode 100644 components/blackbox/values.cue
create mode 100644 components/prometheus/values.cue
-- mkdir-common-config.sh --
mkdir -p config/prometheus
-- blackbox-common-config-header.sh --
cat <<EOF > config/prometheus/blackbox.cue
-- blackbox-common-config-body.cue --
package prometheus
// Schema Definition
#Blackbox: {
// host constrained to a lower case dns label
host: string & =~"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$"
// port constrained to a valid range
port: int & >0 & <=65535
}
// Concrete values must validate against the schema.
blackbox: #Blackbox & {
host: "blackbox"
port: 9115
}
-- blackbox-common-config-git-commit.sh --
git add . && git commit -m 'add blackbox configuration'
-- blackbox-common-config-git-output.txt --
[main 1adcd08] add blackbox configuration
1 file changed, 15 insertions(+)
create mode 100644 components/blackbox.cue
-- common-config-patch.sh --
patch -p1 < values.patch
-- values.patch --
--- a/components/blackbox/values.cue
+++ b/components/blackbox/values.cue
@@ -1,6 +1,11 @@
package holos
+// Import common blackbox configuration
+import "holos.example/config/prometheus"
+
Helm: Values: {
+ fullnameOverride: prometheus.blackbox.host
+
global: {
//# Global image registry to use if it needs to be overriden for some specific use cases (e.g local registries, custom images, ...)
//#
@@ -192,7 +197,7 @@ Helm: Values: {
annotations: {}
labels: {}
type: "ClusterIP"
- port: 9115
+ port: prometheus.blackbox.port
ipDualStack: {
enabled: false
ipFamilies: ["IPv6", "IPv4"]
--- a/components/prometheus/values.cue
+++ b/components/prometheus/values.cue
@@ -1,5 +1,8 @@
package holos
+// Import common blackbox configuration
+import "holos.example/config/prometheus"
+
Helm: Values: {
// yaml-language-server: $schema=values.schema.json
// Default values for prometheus.
@@ -1083,7 +1086,7 @@ Helm: Values: {
target_label: "__param_target"
}, {
target_label: "__address__"
- replacement: "blackbox"
+ replacement: "\(prometheus.blackbox.host):\(prometheus.blackbox.port)"
}, {
source_labels: ["__param_target"]
target_label: "instance"
-- common-config-patch.txt --
patching file 'components/blackbox/values.cue'
patching file 'components/prometheus/values.cue'
-- common-config-rm.sh --
rm values.patch
-- common-config-git.sh --
git add . && git commit -m 'integrate blackbox and prometheus together'
-- common-config-git-output.txt --
[main 4221803] integrate blackbox and prometheus together
2 files changed, 4 insertions(+), 2 deletions(-)
-- reviewing-changes-render-output.txt --
rendered blackbox in 374.810666ms
rendered prometheus in 382.899334ms
rendered platform in 383.270625ms
-- git-diff.sh --
git diff
-- git.diff --
diff --git a/deploy/components/blackbox/blackbox.gen.yaml b/deploy/components/blackbox/blackbox.gen.yaml
index 3db20cd..5336f44 100644
--- a/deploy/components/blackbox/blackbox.gen.yaml
+++ b/deploy/components/blackbox/blackbox.gen.yaml
@@ -7,7 +7,7 @@ metadata:
app.kubernetes.io/name: prometheus-blackbox-exporter
app.kubernetes.io/version: v0.25.0
helm.sh/chart: prometheus-blackbox-exporter-9.0.1
- name: prometheus-blackbox-exporter
+ name: blackbox
namespace: default
---
apiVersion: v1
@@ -31,7 +31,7 @@ metadata:
app.kubernetes.io/name: prometheus-blackbox-exporter
app.kubernetes.io/version: v0.25.0
helm.sh/chart: prometheus-blackbox-exporter-9.0.1
- name: prometheus-blackbox-exporter
+ name: blackbox
namespace: default
---
apiVersion: v1
@@ -43,7 +43,7 @@ metadata:
app.kubernetes.io/name: prometheus-blackbox-exporter
app.kubernetes.io/version: v0.25.0
helm.sh/chart: prometheus-blackbox-exporter-9.0.1
- name: prometheus-blackbox-exporter
+ name: blackbox
namespace: default
spec:
ports:
@@ -65,7 +65,7 @@ metadata:
app.kubernetes.io/name: prometheus-blackbox-exporter
app.kubernetes.io/version: v0.25.0
helm.sh/chart: prometheus-blackbox-exporter-9.0.1
- name: prometheus-blackbox-exporter
+ name: blackbox
namespace: default
spec:
replicas: 1
@@ -119,8 +119,8 @@ spec:
name: config
hostNetwork: false
restartPolicy: Always
- serviceAccountName: prometheus-blackbox-exporter
+ serviceAccountName: blackbox
volumes:
- configMap:
- name: prometheus-blackbox-exporter
+ name: blackbox
name: config
diff --git a/deploy/components/prometheus/prometheus.gen.yaml b/deploy/components/prometheus/prometheus.gen.yaml
index 9e02bce..ab638f0 100644
--- a/deploy/components/prometheus/prometheus.gen.yaml
+++ b/deploy/components/prometheus/prometheus.gen.yaml
@@ -589,7 +589,7 @@ data:
- source_labels:
- __address__
target_label: __param_target
- - replacement: blackbox
+ - replacement: blackbox:9115
target_label: __address__
- source_labels:
- __param_target
-- reviewing-changes-git-commit.sh --
git add . && git commit -m 'render integrated blackbox and prometheus manifests'
-- reviewing-changes-git-output.txt --
[main 67efe0d] render integrated blackbox and prometheus manifests
2 files changed, 7 insertions(+), 7 deletions(-)

View File

@@ -1 +0,0 @@
holos --version

View File

@@ -1,15 +0,0 @@
package prometheus
// Schema Definition
#Blackbox: {
// host constrained to a lower case dns label
host: string & =~"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$"
// port constrained to a valid range
port: int & >0 & <=65535
}
// Concrete values must validate against the schema.
blackbox: #Blackbox & {
host: "blackbox"
port: 9115
}

View File

@@ -1 +0,0 @@
git add . && git commit -m 'add blackbox configuration'

View File

@@ -1,3 +0,0 @@
[main 1adcd08] add blackbox configuration
1 file changed, 15 insertions(+)
create mode 100644 components/blackbox.cue

View File

@@ -1 +0,0 @@
cat <<EOF > config/prometheus/blackbox.cue

View File

@@ -1,15 +0,0 @@
package holos
// Produce a helm chart build plan.
holos: Helm.BuildPlan
Helm: #Helm & {
Chart: {
name: "prometheus-blackbox-exporter"
version: "9.0.1"
repository: {
name: "prometheus-community"
url: "https://prometheus-community.github.io/helm-charts"
}
}
}

View File

@@ -1 +0,0 @@
cat <<EOF > components/blackbox/blackbox.cue

View File

@@ -1,2 +0,0 @@
[main 4221803] integrate blackbox and prometheus together
2 files changed, 4 insertions(+), 2 deletions(-)

View File

@@ -1 +0,0 @@
git add . && git commit -m 'integrate blackbox and prometheus together'

View File

@@ -1 +0,0 @@
patch -p1 < values.patch

View File

@@ -1,2 +0,0 @@
patching file 'components/blackbox/values.cue'
patching file 'components/prometheus/values.cue'

View File

@@ -1 +0,0 @@
git init . && git add . && git commit -m "initial commit"

View File

@@ -1,64 +0,0 @@
diff --git a/deploy/components/blackbox/blackbox.gen.yaml b/deploy/components/blackbox/blackbox.gen.yaml
index 3db20cd..5336f44 100644
--- a/deploy/components/blackbox/blackbox.gen.yaml
+++ b/deploy/components/blackbox/blackbox.gen.yaml
@@ -7,7 +7,7 @@ metadata:
app.kubernetes.io/name: prometheus-blackbox-exporter
app.kubernetes.io/version: v0.25.0
helm.sh/chart: prometheus-blackbox-exporter-9.0.1
- name: prometheus-blackbox-exporter
+ name: blackbox
namespace: default
---
apiVersion: v1
@@ -31,7 +31,7 @@ metadata:
app.kubernetes.io/name: prometheus-blackbox-exporter
app.kubernetes.io/version: v0.25.0
helm.sh/chart: prometheus-blackbox-exporter-9.0.1
- name: prometheus-blackbox-exporter
+ name: blackbox
namespace: default
---
apiVersion: v1
@@ -43,7 +43,7 @@ metadata:
app.kubernetes.io/name: prometheus-blackbox-exporter
app.kubernetes.io/version: v0.25.0
helm.sh/chart: prometheus-blackbox-exporter-9.0.1
- name: prometheus-blackbox-exporter
+ name: blackbox
namespace: default
spec:
ports:
@@ -65,7 +65,7 @@ metadata:
app.kubernetes.io/name: prometheus-blackbox-exporter
app.kubernetes.io/version: v0.25.0
helm.sh/chart: prometheus-blackbox-exporter-9.0.1
- name: prometheus-blackbox-exporter
+ name: blackbox
namespace: default
spec:
replicas: 1
@@ -119,8 +119,8 @@ spec:
name: config
hostNetwork: false
restartPolicy: Always
- serviceAccountName: prometheus-blackbox-exporter
+ serviceAccountName: blackbox
volumes:
- configMap:
- name: prometheus-blackbox-exporter
+ name: blackbox
name: config
diff --git a/deploy/components/prometheus/prometheus.gen.yaml b/deploy/components/prometheus/prometheus.gen.yaml
index 9e02bce..ab638f0 100644
--- a/deploy/components/prometheus/prometheus.gen.yaml
+++ b/deploy/components/prometheus/prometheus.gen.yaml
@@ -589,7 +589,7 @@ data:
- source_labels:
- __address__
target_label: __param_target
- - replacement: blackbox
+ - replacement: blackbox:9115
target_label: __address__
- source_labels:
- __param_target

View File

@@ -1,5 +0,0 @@
holos cue import \
--package holos \
--path 'Helm: Values:' \
--outfile components/blackbox/values.cue \
components/blackbox/vendor/9.0.1/prometheus-blackbox-exporter/values.yaml

View File

@@ -1,5 +0,0 @@
holos cue import \
--package holos \
--path 'Helm: Values:' \
--outfile components/prometheus/values.cue \
components/prometheus/vendor/25.27.0/prometheus/values.yaml

View File

@@ -1 +0,0 @@
git add . && git commit -m 'import values'

View File

@@ -1,4 +0,0 @@
[main 52e90ea] import values
2 files changed, 1815 insertions(+)
create mode 100644 components/blackbox/values.cue
create mode 100644 components/prometheus/values.cue

View File

@@ -1,3 +0,0 @@
rendered blackbox in 365.936792ms
rendered prometheus in 371.855875ms
rendered platform in 372.109916ms

View File

@@ -1,3 +0,0 @@
mkdir holos-helm-values-tutorial
cd holos-helm-values-tutorial
holos init platform v1alpha5

View File

@@ -1 +0,0 @@
mkdir -p config/prometheus

View File

@@ -1 +0,0 @@
mkdir -p components/prometheus components/blackbox

View File

@@ -1,15 +0,0 @@
package holos
// Produce a helm chart build plan.
holos: Helm.BuildPlan
Helm: #Helm & {
Chart: {
name: "prometheus"
version: "25.27.0"
repository: {
name: "prometheus-community"
url: "https://prometheus-community.github.io/helm-charts"
}
}
}

View File

@@ -1 +0,0 @@
cat <<EOF > components/prometheus/prometheus.cue

View File

@@ -1,12 +0,0 @@
package holos
Platform: Components: {
prometheus: {
name: "prometheus"
path: "components/prometheus"
}
blackbox: {
name: "blackbox"
path: "components/blackbox"
}
}

View File

@@ -1,7 +0,0 @@
[main b5df111] add blackbox and prometheus
5 files changed, 1550 insertions(+)
create mode 100644 components/blackbox/blackbox.cue
create mode 100644 components/prometheus/prometheus.cue
create mode 100644 deploy/components/blackbox/blackbox.gen.yaml
create mode 100644 deploy/components/prometheus/prometheus.gen.yaml
create mode 100644 platform/prometheus.cue

View File

@@ -1 +0,0 @@
git add . && git commit -m 'add blackbox and prometheus'

View File

@@ -1 +0,0 @@
cat <<EOF > platform/prometheus.cue

View File

@@ -1,5 +0,0 @@
cached prometheus-blackbox-exporter 9.0.1
rendered blackbox in 3.825430417s
cached prometheus 25.27.0
rendered prometheus in 4.840089667s
rendered platform in 4.840137792s

View File

@@ -1 +0,0 @@
holos render platform

View File

@@ -1 +0,0 @@
git add . && git commit -m 'render integrated blackbox and prometheus manifests'

View File

@@ -1,2 +0,0 @@
[main 67efe0d] render integrated blackbox and prometheus manifests
2 files changed, 7 insertions(+), 7 deletions(-)

View File

@@ -1,3 +0,0 @@
rendered blackbox in 374.810666ms
rendered prometheus in 382.899334ms
rendered platform in 383.270625ms

View File

@@ -1,4 +0,0 @@
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"

View File

@@ -1,43 +0,0 @@
--- a/components/blackbox/values.cue
+++ b/components/blackbox/values.cue
@@ -1,6 +1,11 @@
package holos
+// Import common blackbox configuration
+import "holos.example/config/prometheus"
+
Helm: Values: {
+ fullnameOverride: prometheus.blackbox.host
+
global: {
//# Global image registry to use if it needs to be overriden for some specific use cases (e.g local registries, custom images, ...)
//#
@@ -192,7 +197,7 @@ Helm: Values: {
annotations: {}
labels: {}
type: "ClusterIP"
- port: 9115
+ port: prometheus.blackbox.port
ipDualStack: {
enabled: false
ipFamilies: ["IPv6", "IPv4"]
--- a/components/prometheus/values.cue
+++ b/components/prometheus/values.cue
@@ -1,5 +1,8 @@
package holos
+// Import common blackbox configuration
+import "holos.example/config/prometheus"
+
Helm: Values: {
// yaml-language-server: $schema=values.schema.json
// Default values for prometheus.
@@ -1083,7 +1086,7 @@ Helm: Values: {
target_label: "__param_target"
}, {
target_label: "__address__"
- replacement: "blackbox"
+ replacement: "\(prometheus.blackbox.host):\(prometheus.blackbox.port)"
}, {
source_labels: ["__param_target"]
target_label: "instance"

Some files were not shown because too many files have changed in this diff Show More