Skip to main content
Version: v1alpha5

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:

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.

mkdir -p components/podinfo

Create the CUE configuration for the example podinfo component.

cat <<EOF >components/podinfo/podinfo.cue
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)
}
}
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.

holos cue export --out=yaml --expression Environments
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.

cat <<EOF > environments.schema.cue
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
}
}
EOF

Adding configuration

With a schema defined, we can fill in the concrete values.

cat <<EOF > environments.cue
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
}
EOF

Inspecting the configuration

Inspect the Environments data structure to verify the schema and concrete values are what we want.

holos cue export --out=yaml --expression Environments

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.

cat <<EOF > platform/podinfo.cue
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)"
}

Using the environment

Now we can configure podinfo based on the jurisdiction of the environment.

cat <<EOF > components/podinfo/cookie-consent.cue
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]
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.

holos show buildplans --selector app.holos.run/city=ams

In Portland cookie consent is disabled.

holos show buildplans --selector app.holos.run/city=pdx

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.