Config Pattern
Config Pattern
This example demonstrates how config
resources can be used to parameterize general purpose resource definitions. The config
resource can be used to parameterize a Resource Definition for different contexts such as environment type and even be used by development teams to further specialize a resource for their purpose.
How the example works
This example is made up of:
- A single
s3
resource definition. (Implemented using the Echo driver for simplicity for this example) - A
config
resource that provides default configuration as specified by the platform team. - A
config
resource that can be used by developers to override some configuration values.
The Resource Graph for production with developer overrides would look like:
flowchart LR
WL[Workload] -->|score resource dependency| S3(type: s3, id: s3-bucket, class: default)
S3 --> CONF_S3(type: config, id: s3-bucket, class: default)
CONF_S3 --> CONF_S3_DEV_OVERRIDE(type: config, id: s3-bucket, class: developer-overrides)
The example demonstrates how:
- different configurations can be used in different environments while using the same Terraform Resource Definition
- developers can optionally provide overrides that can be controlled by the platform team.
There are 3 resource definitions:
-
The
s3
Resource Definitiondef-s3-base.yaml
defines the underlying “base” resource. In this case it is very simple - implemented using the Echo Driver. It takes 2 parameters -region
andbucket
- returning both of these. -
The first
config
Resource Definitiondef-config-platform-defaults.yaml
does two things:- Provide default configuration values supplied by the platform team.
- Reference the overrides that developers can supply via their own
config
Resource Definition.
These config also provide guardrails in that only certain values can be overridden. In this example, developers can override the
prefix
and thename
properties but nottags
orregion
. -
The last
config
Resource Definitiondef-config-developer-overrides.yaml
allows developers to provide their overrides that can tune the resource that they request.
In practice, you may choose to maintain the Resource Definitions for the platform team and the developers in different git repositories to separate out access permissions.
Run the demo
Prerequisites
See the prerequisites section
in the README at the root of this section.
In addition, the environment variable HUMANITEC_APP
should be set to example-config-pattern
.
Cost
This example will result in one Pod being deployed.
Deploy the example
-
Create a new app:
humctl create app "${HUMANITEC_APP}"
-
Register the Resource Definitions:
mkdir resource-definitions cp def-*.yaml ./resource-definitions humctl apply -f ./resource-definitions
-
Deploy the Score workload:
humctl score deploy
-
Inspect the effective environment variables of your workload:
- Open the portal at https://app.humanitec.io/orgs/${HUMANITEC_ORG}/apps/example-config-pattern/envs/development
- Select the
example-config-pattern-workload
and inspect the log output of thebusybox
container. - Check the values of the
BUCKET_NAME
and orBUCKET_REGION
variables.
Explore the example
-
Change the
name
and orprefix
properties indef-config-developer-overrides.yaml
. Try addingregion
. -
Redeploy:
humctl score deploy
-
Observe if the
BUCKET_NAME
and orBUCKET_REGION
variables have changed.
Clean up the example
-
Delete the Application:
humctl delete app "${HUMANITEC_APP}"
-
Delete the Resource Definitions:
humctl delete -f ./resource-definitions rm -rf ./resource-definitions
score.yaml
(
view on GitHub
)
:
apiVersion: score.dev/v1b1
metadata:
name: example-config-pattern-workload
containers:
busybox:
image: busybox
command:
- /bin/sh
args:
- "-c"
# This will output all of the environment variables in the container to
# STDOUT every 15 seconds. This can be seen in the container logs in the
# Humanitec UI.
- "while true; do set; sleep 15; done"
variables:
BUCKET_NAME: ${resources.s3-bucket.bucket}
BUCKET_REGION: ${resources.s3-bucket.region}
resources:
s3-bucket:
type: s3
Resource Definitions
def-config-developer-overrides.yaml
(
view on GitHub
)
:
apiVersion: entity.humanitec.io/v1b1
kind: Definition
metadata:
id: config-pattern-example-config-developer-overrides
entity:
name: config-pattern-example-config-developer-overrides
type: config
criteria:
- app_id: example-config-pattern
class: developer-overrides
driver_type: humanitec/echo
driver_inputs:
values:
# Here we only override prefix. But try also overriding "name" and "region".
# "name" will be used, "region" will be ignored.
prefix: overridden-
def-config-platform-defaults.yaml
(
view on GitHub
)
:
apiVersion: entity.humanitec.io/v1b1
kind: Definition
metadata:
id: config-pattern-example-config-platform-defaults
entity:
name: config-pattern-example-config-platform-defaults
type: config
criteria:
- app_id: example-config-pattern
driver_type: humanitec/template
driver_inputs:
values:
templates:
init: |
defaults:
# These are values defined by the platform team to be used by the terraform module
prefix: ""
region: eu-central-1
name: {{ "${context.res.id}" | splitList "." | last }}-${context.app.id}-${context.org.id}
tags:
example: config-pattern-example
env: ${context.env.id}
outputs: |
{{- $overrides := .driver.values.overrides }}
# Loop through all the default keys - this way we don't introduce additional keys from
# the developer overrides.
{{- range $key, $val := .init.defaults }}
# Don't allow overrides of some keys
{{- if (list "region" "tags") | has $key }}
{{ $key }}: {{ $val | toRawJson }}
{{- else }}
{{ $key }}: {{ $overrides | dig $key $val | toRawJson }}
{{- end}}
{{- end }}
# Generate some additional keys
bucket_name: {{ $overrides.prefix | default .init.defaults.prefix }}{{ $overrides.name | default .init.defaults.name }}
overrides: ${resources['config.developer-overrides'].values}
def-s3-base.yaml
(
view on GitHub
)
:
apiVersion: entity.humanitec.io/v1b1
kind: Definition
metadata:
id: config-pattern-example-s3-base
entity:
name: config-pattern-example-s3-base
type: s3
criteria:
- app_id: example-config-pattern
driver_type: humanitec/echo
driver_inputs:
values:
# Placeholders of the form "${resources.config.outputs."" will
# automatically be provisioned with res_id and class equal to this
# instance that will be provisioned.
#
# See github.com/humanitec-architecture/example-library//propagate-id and github.com/humanitec-architecture/example-library//propagate-class for more details.
region: ${resources.config.outputs.region}
bucket: ${resources.config.outputs.bucket_name}
def-config-developer-overrides.tf
(
view on GitHub
)
:
resource "humanitec_resource_definition" "config-pattern-example-config-developer-overrides" {
driver_type = "humanitec/echo"
id = "config-pattern-example-config-developer-overrides"
name = "config-pattern-example-config-developer-overrides"
type = "config"
driver_inputs = {
values_string = jsonencode({
"prefix" = "overridden-"
})
}
}
resource "humanitec_resource_definition_criteria" "config-pattern-example-config-developer-overrides_criteria_0" {
resource_definition_id = resource.humanitec_resource_definition.config-pattern-example-config-developer-overrides.id
app_id = "example-config-pattern"
class = "developer-overrides"
}
def-config-platform-defaults.tf
(
view on GitHub
)
:
resource "humanitec_resource_definition" "config-pattern-example-config-platform-defaults" {
driver_type = "humanitec/template"
id = "config-pattern-example-config-platform-defaults"
name = "config-pattern-example-config-platform-defaults"
type = "config"
driver_inputs = {
values_string = jsonencode({
"templates" = {
"init" = <<END_OF_TEXT
defaults:
# These are values defined by the platform team to be used by the terraform module
prefix: ""
region: eu-central-1
name: {{ "$${context.res.id}" | splitList "." | last }}-$${context.app.id}-$${context.org.id}
tags:
example: config-pattern-example
env: $${context.env.id}
END_OF_TEXT
"outputs" = <<END_OF_TEXT
{{- $overrides := .driver.values.overrides }}
# Loop through all the default keys - this way we don't introduce additional keys from
# the developer overrides.
{{- range $key, $val := .init.defaults }}
# Don't allow overrides of some keys
{{- if (list "region" "tags") | has $key }}
{{ $key }}: {{ $val | toRawJson }}
{{- else }}
{{ $key }}: {{ $overrides | dig $key $val | toRawJson }}
{{- end}}
{{- end }}
# Generate some additional keys
bucket_name: {{ $overrides.prefix | default .init.defaults.prefix }}{{ $overrides.name | default .init.defaults.name }}
END_OF_TEXT
}
"overrides" = "$${resources['config.developer-overrides'].values}"
})
}
}
resource "humanitec_resource_definition_criteria" "config-pattern-example-config-platform-defaults_criteria_0" {
resource_definition_id = resource.humanitec_resource_definition.config-pattern-example-config-platform-defaults.id
app_id = "example-config-pattern"
}
def-s3-base.tf
(
view on GitHub
)
:
resource "humanitec_resource_definition" "config-pattern-example-s3-base" {
driver_type = "humanitec/echo"
id = "config-pattern-example-s3-base"
name = "config-pattern-example-s3-base"
type = "s3"
driver_inputs = {
values_string = jsonencode({
"region" = "$${resources.config.outputs.region}"
"bucket" = "$${resources.config.outputs.bucket_name}"
})
}
}
resource "humanitec_resource_definition_criteria" "config-pattern-example-s3-base_criteria_0" {
resource_definition_id = resource.humanitec_resource_definition.config-pattern-example-s3-base.id
app_id = "example-config-pattern"
}