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 
s3resource definition. (Implemented using the Echo driver for simplicity for this example) - A 
configresource that provides default configuration as specified by the platform team. - A 
configresource 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
s3Resource Definitiondef-s3-base.yamldefines the underlying “base” resource. In this case it is very simple - implemented using the Echo Driver. It takes 2 parameters -regionandbucket- returning both of these. - 
The first
configResource Definitiondef-config-platform-defaults.yamldoes two things:- Provide default configuration values supplied by the platform team.
 - Reference the overrides that developers can supply via their own 
configResource Definition. 
These config also provide guardrails in that only certain values can be overridden. In this example, developers can override the
prefixand thenameproperties but nottagsorregion. - 
The last
configResource Definitiondef-config-developer-overrides.yamlallows 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-workloadand inspect the log output of thebusyboxcontainer. - Check the values of the 
BUCKET_NAMEand orBUCKET_REGIONvariables. 
Explore the example
- 
Change the
nameand orprefixproperties indef-config-developer-overrides.yaml. Try addingregion. - 
Redeploy:
humctl score deploy - 
Observe if the
BUCKET_NAMEand orBUCKET_REGIONvariables 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"
}