Variable Groups

Environment variable groups are similar to regular Environment Variables and support the same features like being secretable and interpolated in other parts of the YAML Configuration, with two key differences:

  1. They are not auto injected to containers, you decide which group should be applied to what component
  2. They do no inherit from Project Variables

📘

Compatibility

The regular Environment Variables defined on the environment or project will work alongside groups.

Using groups allows fine grained control on which components have access to each specific group.

Example

To create a new group simply add it to the YAML under environmentVariablesGroups:

kind: Environment
type: primary
name: Var Group Example
environmentVariablesGroups:
    rabbitmq:
        RABBITMQ_DEFAULT_USER: example
        RABBITMQ_DEFAULT_PASS: SECRET[password]
        RABBITMQ_NODE_PORT: 5672
        RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: |
            -ra wal_max_size_bytes 32000000
            -rabbit vm_memory_high_watermark {absolute,420000000}
    queue:
        QUEUE_HOST: localrabbitmq
        QUEUE_DOMAIN: '{{ env.varGroups.queue.QUEUE_HOST }}:{{ env.varGroups.rabbitmq.RABBITMQ_NODE_PORT }}'
        QUEUE_AUTH: '{{ env.varGroups.rabbitmq.RABBITMQ_DEFAULT_USER }}:{{ env.varGroups.rabbitmq.RABBITMQ_DEFAULT_PASS }}'
        QUEUE_DSN: 'amqp://{{ env.varGroups.queue.QUEUE_AUTH }}@{{ env.varGroups.queue.QUEUE_DOMAIN }}/'
    data:
        just: some
        data: here
components:
    -
        kind: Application
        name: rabbitmq
        dockerCompose:
            image: 'rabbitmq:latest'
            ports:
                # no interpolation support in ports
                - '5672:5672'
            environmentVariablesGroups:
                - rabbitmq
            hostname: '{{ env.varGroups.queue.QUEUE_HOST }}'
    -
        kind: Application
        name: backend
        dockerCompose:
            image: 'nginx:latest'
            environmentVariablesGroups:
                - queue
    -
        kind: Application
        name: worker
        dockerCompose:
            image: 'python:latest'
            environmentVariablesGroups:
                - rabbitmq
                - queue
            environment:
                LOG_LEVEL: DEBUG
    -
        kind: SidecarContainer
        name: monitor
        dockerCompose:
            image: docker:20.10.17-dind
            command: 'tail -f /dev/null'
            environmentVariablesGroups:
                - queue
            environment:
                LOG_LEVEL: CRITICAL
    -
        kind: Application
        name: frontend
        dockerCompose:
            image: 'nginx:latest'
        pod:
            sidecar_containers:
                -
                    from: monitor
                    name: monitor-frontend
                    # as a sidecar, it will also inherit the group defined on the sidecar component
                    environmentVariablesGroups:
                        - rabbitmq
                    # as a sidecar, it will also inherit the environment from the main component
                    environment:
                        LOG_LEVEL: ERROR

In the above example we do the following:

  1. Set the rabbitmq group with the environment variables known to RabbitMQ
  2. Set the queue group with ready a ready to use DSN
  3. The rabbitmq service uses the rabbitmq group
  4. The backend service uses the queue group because it can handle the QUEUE_DSN
  5. The worker service uses the both groups as an example

Injection precedence

When you are applying a group to a Bunnyshell Component, it will override Project or Environment scoped variables.

When using multiple groups the order in which they are defined within the component is the same order they will override any duplicate variables.

In the above example, the worker service uses both rabbitmq and queue groups, meaning any common variables defined in both groups will be overridden by queue.

Kubernetes Resources

Upon deploying the above example, it will also generate a Kubernetes ConfigMap for each group in the environment namespace, with the name environ-{{ groupName }} even when not directly used by components.

apiVersion: v1
data:
    RABBITMQ_DEFAULT_USER: example
    RABBITMQ_DEFAULT_PASS: password
    RABBITMQ_NODE_PORT: '5672'
    RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: "-ra wal_max_size_bytes 32000000\n-rabbit vm_memory_high_watermark {absolute,420000000}\n"
kind: ConfigMap
metadata:
    creationTimestamp: null
    labels: {  }
    name: environ-rabbitmq

apiVersion: v1
data:
    QUEUE_HOST: localrabbitmq
    QUEUE_DOMAIN: localrabbitmq:5672
    QUEUE_AUTH: example:password
    QUEUE_DSN: 'amqp://example:password@localrabbitmq:5672/'
kind: ConfigMap
metadata:
    creationTimestamp: null
    labels: {  }
    name: environ-queue

apiVersion: v1
data:
    just: some
    data: here
kind: ConfigMap
metadata:
    creationTimestamp: null
    labels: {  }
    name: environ-data

📘

Usage

You can additionally use a ConfigMap with Helm Components or Kubernetes Manifest Components

Advanced Usage

You can use the available filters and create custom encodings from your env var groups.

Dot Env File

{{ env.varGroups.queue | merge ({
    APP_ENV: 'prod'
}) | kv_encode }}

When used against the above bunnyshell.yaml configuration:

QUEUE_HOST=localrabbitmq
QUEUE_DOMAIN=localrabbitmq:5672
QUEUE_AUTH=example:password
QUEUE_DSN=amqp://example:password@localrabbitmq:5672/
APP_ENV=prod

Yaml Configuration

apiVersion: v1
kind: ConfigMap
metadata:
    name: {{ env.name|lower|slug }}-custom-config-map
data:
{{ env.varGroups.queue | merge ({
    APP_ENV: prod
}) | yaml_encode({
    inline: false,
    indent: 4,
}) }}

When used against the above bunnyshell.yaml configuration:

apiVersion: v1
kind: ConfigMap
metadata:
    name: var-group-example-custom-config-map
data:
    QUEUE_HOST: localrabbitmq
    QUEUE_DOMAIN: localrabbitmq:5672
    QUEUE_AUTH: example:password
    QUEUE_DSN: 'amqp://example:password@localrabbitmq:5672/'
    APP_ENV: prod