Resource Graph Patterns

Pattern name

Resource Type

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:

  1. The s3 Resource Definition def-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 and bucket - returning both of these.

  2. The first config Resource Definition def-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 the name properties but not tags or region.

  3. The last config Resource Definition def-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

  1. Create a new app:

    humctl create app "${HUMANITEC_APP}"
    
  2. Register the Resource Definitions:

    mkdir resource-definitions
    cp def-*.yaml ./resource-definitions
    humctl apply -f ./resource-definitions
    
  3. Deploy the Score workload:

    humctl score deploy
    
  4. 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 the busybox container.
  • Check the values of the BUCKET_NAME and or BUCKET_REGION variables.

Explore the example

  1. Change the name and or prefix properties in def-config-developer-overrides.yaml. Try adding region.

  2. Redeploy:

    humctl score deploy
    
  3. Observe if the BUCKET_NAME and or BUCKET_REGION variables have changed.

Clean up the example

  1. Delete the Application:

    humctl delete app "${HUMANITEC_APP}"
    
  2. 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"
}

Top