Stacks

Bespin revolves around the concept of a cloudformation stack. Defining them is one of the required options in the Configuration.

A cloudformation stack has two parts to it:

The template file

Cloudformation is defined by a template file - see Cloudformation template basics

Currently bespin supports the JSON and YAML Cloudformation formats.

The parameters

Cloudformation has the idea of parameters, where you define variables in your stack and then provide values for those variables at creation time.

Bespin provides the option of either specifying a file containing these values or, more conveniently, you may specify them inline with the configuration as a yaml dictionary.

So if you have the following directory structure:

/my-project/
  bespin.yml
  app.json
  params.json

And the following configuration:

---

environments:
  dev:
    account_id: "123456789"

stacks:
  app:
    stack_name: my-application
    stack_json: "{config_root}/app.json"
    params_json: "{config_root}/params.json"

Then bespin deploy dev app will deploy the app.json using params.json as the parameters.

Where params.json looks like:

[ { "ParameterKey": "Key1"
  , "ParameterValue": "Value1"
  }
, { "ParameterKey": "Key2"
  , "ParameterValue": "Value2"
  }
]

An equivalent params.yaml file would look like:

---

Key1: Value1
Key2: Value2

Alternatively you can have inline the parameters like so:

---

environments:
  dev:
    account_id: "123456789"

stacks
  app:
    stack_name: my-application
    stack_json: "{config_root}/app.json"

    params_yaml:
      Key1: Value1
      Key2: Value2

Note

The stack_json and stack_yaml will default to “{config_root}/{_key_name_1}.json” and “{config_root}/{_key_name_1}.yaml”. This means if your stack json is the same name as the stack and next to your configuration, then you don’t need to specify stack_json.

Defining variables

You can refer to variables defined in your configuration inside params_yaml using a XXX_<VARIABLE>_XXX syntax. So if you have defined a variable called my_ami then XXX_MY_AMI_XXX inside your params_yaml values will be replaced with the value of that variable.

Note

This syntax is available in addition to the Configuration Formatter. Formatter {} syntax will only reference config values, and gets interpreted when loading the configuration. Whereas the XXX_<VARIABLE>_XXX variable may be sourced from elsewhere (see below: dynamic variables, environment variables) and can be replaced at runtime.

So let’s say I have the following configuration:

---

vars:
  azs: "ap-southeast-2a,ap-southeast-2b"

environments:
  dev:
    account_id: "123456789"
    vars:
      vpcid: vpc-123456

  prod:
    account_id: "987654321"
    vars:
      vpcid: vpc-654321

stacks:
  app:
    stack_name: my-application
    vars:
      ami: ami-4321

    environments:
      dev:
        vars:
          min_size: 0

      prod:
        vars:
          min_size: 2

    params_yaml:
      ami: XXX_AMI_XXX
      AZs: XXX_AZS_XXX
      VpcId: XXX_VPCID_XXX
      MinSize: XXX_MIN_SIZE_XXX

Then you’ll get the following outputs:

$ bespin params dev app
my-application
[
    {
        "ParameterValue": "vpc-123456",
        "ParameterKey": "VPCId"
    },
    {
        "ParameterValue": "ap-southeast-2a,ap-southeast-2b",
        "ParameterKey": "AZs"
    },
    {
        "ParameterValue": "ami-4321",
        "ParameterKey": "ami"
    }
]

$ bespin params prod app
my-application
[
    {
        "ParameterValue": "vpc-654321",
        "ParameterKey": "VPCId"
    },
    {
        "ParameterValue": "ap-southeast-2a,ap-southeast-2b",
        "ParameterKey": "AZs"
    },
    {
        "ParameterValue": "ami-4321",
        "ParameterKey": "ami"
    }
]

If you’re looking closely enough you may notice that there is a hierarchy of variables in the configuration. Bespin will essentially collapse this hierarchy into one dictionary of variables at runtime before using them.

The order is:

<root>
<environment>
<stack>
<stack_environment>

Where values of the same name are overridden.

This allows you to have:

  • Variables across all stacks for all environments
  • Variables across all stacks for particular environments
  • Variables specific to a stack for all environments
  • Variables specific to a stack for particular environments

Note

The XXX_<VARIABLE>_XXX syntax is a search and replace, so you can do something like:

---

environments:
  dev:
    account_id: "123456789"
    vars:
      subnet_a: subnet-12345
      subnet_b: subnet-67890

stacks:
  app:
    stack_name: my-application

    params_yaml:
      subnets: XXX_SUBNET_A_XXX,XXX_SUBNET_B_XXX

and reference more than one variable and intermingle with other characters.

Dynamic Variables

When you define a variable, you may also specify a list of two items:

---

vars:
  vpcid: [vpc-base, VpcId]
  zoneid: ["{stacks.dns-public}", ZoneId]

This is a special syntax and stands for [<stack_name>, <output_name>] and will dynamically find the specified Cloudformation output for that stack.

If the stack is in bespin’s config it can be referenced directly using the Configuration Formatter, ie: ["{stacks.my_stack}", <output_name>]. This will use the stack_name from my_stack and also add my_stack to this stack’s build_first dependencies.

For those unfamiliar with cloudformation, it allows you to define Outputs for your stacks. These outputs are essentially a Key-Value store of template defined strings.

So in the example above, the vpcid variable would resolve to the VpcId Output from the vpc-base cloudformation stack in the environment being deployed to.

Environment Variables

You may populate variables with environment variables.

First you must specify env as a list of environment variables that need to be defined and then you may refer to them using XXX_<VARIABLE>_XXX.

For example:

---

environments:
  dev:
    account_id: "123456789"

stacks:
  app:
    stack_name: my-application

    env:
      - BUILD_NUMBER
      - GIT_COMMIT

    params_yaml:
      Version: app-XXX_BUILD_NUMBER_XXX

Environment variables can also be defined with defaults or overrides.

“BUILD_NUMBER”
No default is specified, so if this variable isn’t in the environment at runtime then bespin will complain and quit.
“BUILD_NUMBER:123”
A default has been specified, so if it’s not in the environment at runtime, bespin will populate this variable with the value “123”
“BUILD_NUMBER=123”
An override has been specified. This means that regardless of whether this environment variable has been specified or not, it will be populated with the value of “123”

Note

To use environment variables in stack_name refer to Stack’s stack_name and stack_name_env Configuration documentation.

Passwords

Bespin configuration can store KMS encrypted passwords. Environments can have different passwords, and optionally a different encryption key. If an environment KMSMasterKey override is provided a new crypto_text must obviously also be provided.

Example config:

---

environments:
  dev:
    account_id: 123456789
  prod:
    account_id: 987654321

passwords:
  my_secure_password:
    KMSMasterKey: "arn:aws:kms:ap-southeast-2:111111111:alias/developer_key"
    crypto_text: "EXAMPLEZdnUptmwQqlCnQIBEIAewbM7Amw786ZMGBzvqtpnWmK/Ou0jc3RygppQypuB"

    # environment 'prod' override
    prod:
      KMSMasterKey: "arn:aws:kms:ap-southeast-2:111111111:key/f65a25e4-1234-4195-8398-a4fcd2ba9c3f"
      crypto_text: "EXAMPLExCDgTs6i+kaQIBEIAef3P/39KEDRafROn0x+PkKZDH9JLPPBnTaVXz+KPj"

Passwords can be referenced via {passwords.name.crypto_text} and the correct value for the environment will be used.

Passwords can be encrypted using bespin encrypt_password [environment] [name]. The user will be prompted to enter the plaintext password via Python getpass and then bespin will encrypt using the passwords.name configuration for environment and output the crypto_text to stdout.

Password decryption

Warning

Care should be taken when passing around decrypted passwords as bespin makes no effort to ensure the password is not logged.

Bespin has support for decrypting passwords, though extreme caution should be taken when doing so. Under best practice, decrypted passwords should NOT be referenced in bespin configuration.

Cloudformation parameters should always be passed in their encrypted form and decrypted inside Cloudformation using Custom Resources (if needed).

Users implementing custom task code can reference the plaintext decryption via passwords.name.decrypted.