github cloudposse/atmos v1.32.0

latest releases: v1.88.1, v1, v1.88.0...
18 months ago

what

why

  • Allow creating complex hierarchical stack configurations and make them DRY
  • In Hierarchical Inheritance, every component can act as a base component for one or more child (derived) components, and each child component can inherit from one of more base components.

description

Multilevel Inheritance

Multilevel Inheritance is used when an Atmos component inherits from a base Atmos component, which in turn inherits from another base Atmos component.

In the diagram below, ComponentC directly inherits from ComponentB.
ComponentB directly inherits from ComponentA.

After this Multilevel Inheritance chain gets processed by Atmos, ComponentC will inherit all the configurations (vars, settings, env and other sections) from both ComponentB and ComponentA.

Note that ComponentB overrides the values from ComponentA. ComponentC overrides the values from both ComponentB and ComponentA.


classDiagram
      ComponentA --> ComponentB
      ComponentB --> ComponentC
      ComponentA : vars
      ComponentA : settings
      ComponentA : env
      class ComponentB {
          vars
          settings
          env
          metadata:
            inherits:
              - ComponentA  
      }
      class ComponentC {
          vars
          settings
          env
          metadata:
            inherits:
              - ComponentB  
      }

Hierarchical Inheritance

Hierarchical Inheritance is a combination of Multiple Inheritance and Multilevel Inheritance.

In Hierarchical Inheritance, every component can act as a base component for one or more child (derived) components, and each child component can inherit from one of more base components.


classDiagram
      ComponentA --> ComponentB
      ComponentA --> ComponentC
      ComponentB --> ComponentD
      ComponentB --> ComponentE
      ComponentC --> ComponentF
      ComponentC --> ComponentG
      ComponentH --> ComponentE
      ComponentI --> ComponentG
      ComponentA : vars
      ComponentA : settings
      ComponentA : env
      class ComponentB {
          vars
          settings
          env
          metadata:
            inherits:
              - ComponentA  
      }
      class ComponentC {
          vars
          settings
          env
          metadata:
            inherits:
              - ComponentA  
      }
      class ComponentD {
          vars
          settings
          env
          metadata:
            inherits:
              - ComponentB  
      }
      class ComponentE {
          vars
          settings
          env
          metadata:
            inherits:
              - ComponentB  
              - ComponentH  
      }
      class ComponentF {
          vars
          settings
          env
          metadata:
            inherits:
              - ComponentC  
      }
      class ComponentG {
          vars
          settings
          env
          metadata:
            inherits:
              - ComponentI  
              - ComponentC  
      }
      class ComponentH {
          vars
          settings
          env
      }
      class ComponentI {
          vars
          settings
          env
      }

In the diagram above:

  • ComponentA is the base component of the whole hierarchy

  • ComponentB and ComponentC inherit from ComponentA

  • ComponentD inherits from ComponentB directly, and from ComponentA via Multilevel Inheritance

  • ComponentE is an example of using both Multiple Inheritance and Multilevel Inheritance.
    It inherits from ComponentB and ComponentH directly, and from ComponentA via Multilevel Inheritance


For ComponentE, the inherited components are processed and deep-merged in the order they are specified in the inherits list:

  • ComponentB overrides the configuration from ComponentA

  • ComponentH overrides the configuration from ComponentB and ComponentA (since it's defined after ComponentB in the inherits section)

  • And finally, ComponentE overrides ComponentH, ComponentB and ComponentA


For ComponentG:

  • ComponentI is processed first (since it's the first item in the inherits list)

  • Then ComponentA is processed (since it's the base component for ComponentC which is the second item in the inherits list)

  • Then ComponentC is processed, and it overrides the configuration from ComponentA and ComponentI

  • And finally, ComponentG is processed, and it overrides ComponentC, ComponentA and ComponentI

Hierarchical Inheritance Example

Let's consider the following configuration for Atmos components base-component-1, base-component-2, derived-component-1 and derived-component-2:

components:
  terraform:

    base-component-1:
      metadata:
        type: abstract
      vars:
        hierarchical_inheritance_test: "base-component-1"

    base-component-2:
      metadata:
        type: abstract
      vars:
        hierarchical_inheritance_test: "base-component-2"

    derived-component-1:
      metadata:
        component: "test/test-component"
        inherits:
          - base-component-1
      vars: {}

    derived-component-2:
      metadata:
        component: "test/test-component"
        inherits:
          - base-component-2
          - derived-component-1
      vars: {}

This configuration can be represented by the following diagram:


classDiagram
      `base-component-1` --> `derived-component-1`
      `derived-component-1` --> `derived-component-2`
      `base-component-2` --> `derived-component-2`
      class `base-component-1` {
          settings
          env
          vars:
            hierarchical_inheritance_test: base-component-1
      }
      class `base-component-2` {
          settings
          env
          vars:
            hierarchical_inheritance_test: base-component-2
      }
      class `derived-component-1` {
          settings
          env
          vars
          metadata:
            inherits:
              - base-component-1  
      }
      class `derived-component-2` {
          settings
          env
          vars
          metadata:
            inherits:
              - base-component-2  
              - derived-component-1  
      }

In the configuration above, derived-component-1 inherits from base-component-1.

derived-component-2 inherits from base-component-2 and derived-component-1 via Multiple Inheritance, and from base-component-1 via Multilevel Inheritance.

The derived-component-2 component is processed in the following order:

  • base-component-2 is processed first (since it's the first item in the inherits list)

  • Then base-component-1 is processed (since it's the base component for derived-component-1 which is the second item in the inherits list), and
    it overrides the configuration from base-component-2

  • Then derived-component-1 is processed, and it overrides the configuration from base-component-2 and base-component-1

  • And finally, derived-component-2 is processed, and it overrides derived-component-1, base-component-1 and base-component-2

When we run the following command to provision the derived-component-2 component:

atmos terraform plan derived-component-2 -s tenant1-ue2-test-1

Atmos will show the following output:

Variables for the component 'derived-component-2' in the stack 'tenant1-ue2-test-1':
environment: ue2
hierarchical_inheritance_test: base-component-1
namespace: cp
region: us-east-2
stage: test-1
tenant: tenant1

Command info:
Terraform binary: terraform
Terraform command: plan
Component: derived-component-2
Terraform component: test/test-component
Inheritance: derived-component-2 -> derived-component-1 -> base-component-1 -> base-component-2

Note that the hierarchical_inheritance_test variable was inherited from base-component-1 because it overrode the configuration from base-component-2.


If we change the order of the components in the inherits list for derived-component-2:

components:
  terraform:

    derived-component-2:
      metadata:
        component: "test/test-component"
        inherits:
          - derived-component-1
          - base-component-2
      vars: {}

base-component-2 will be processed after base-component-1 and derived-component-1, and the hierarchical_inheritance_test variable will be inherited from base-component-2:

Variables for the component 'derived-component-2' in the stack 'tenant1-ue2-test-1':
environment: ue2
hierarchical_inheritance_test: base-component-2
namespace: cp
region: us-east-2
stage: test-1
tenant: tenant1

Command info:
Terraform binary: terraform
Terraform command: plan
Component: derived-component-2
Terraform component: test/test-component
Inheritance: derived-component-2 -> base-component-2 -> derived-component-1 -> base-component-1

Don't miss a new atmos release

NewReleases is sending notifications on new releases.