Skip to content

cultureamp/cfparams

Repository files navigation

cfparams

Wrangle CloudFormation parameters.

Reads expected parameter keys from a CloudFormation template, accepts values from command line or YAML file, emits JSON suitable for aws cloudformation *-stack --parameters="..." commands. Supports the use of default and previous values.

Installation Instructions

  • If you use Homebrew, you can simply run: brew install cultureamp/tap/cfparams (only in the Culture Amp organization at the moment, sorry).
  • Alternatively, if you want the latest version, download the executable directly from the release page
  • Rename and move the cfparams executable to your path, e.g: mv cfparams /usr/local/bin (or move to your favourite spot and symlink).
  • To check whether your installation is successful, run the command cfparams and the output would be similar to:
    $ cfparams
    CloudFormation template or tags file required
        e.g: --template=cloudformation.yaml
        e.g: --tags=tags-foo.yaml
  • If it still doesn't work you may need to hash -r to clear the path cache or restart the shell

Example use-cases

CloudFormation template excerpt describing an ECS service to be provisioned onto an existing ECS cluster.

# cfn.yaml excerpt
Parameters:
  Greeting:
    Type: String
    Description: greeting message to send
    Default: Hello
  Recipient:
    Description: name of the greeting recipient
    Type: String
  ImageRepo:
    Type: String
    Description: repository of Docker image to run
    Default: "123.dkr.ecr.us-east-1.amazonaws.com/greeting"
  ImageTag:
    Type: String
    Description: tag of Docker image to run
    Default: latest
  Cluster:
    Description: ECS cluster ID to run service on
    Type: String

Creating ECS service CloudFormation stack

Launching the CloudFormation stack for the first time. Accept some defaults from the template, specify all other parameters.

params="$(
  cfparams --template=cfn.yaml --accept-defaults --no-previous \
    Recipient=world ImageTag=v1 Cluster=nanoservices
)"
  • --template loads supported Parameters from a CloudFormation template.
  • --accept-defaults omits keys that have a default in the CloudFormation template.
  • --no-previous means fail if a key has no default in the template and isn't specified on the command line. Without this option, those keys will be auto-filled as "UsePreviousValue": true.

Resulting JSON:

[
  {"ParameterKey": "Recipient", "ParameterValue": "world"},
  {"ParameterKey": "ImageTag", "ParameterValue": "v1"},
  {"ParameterKey": "Cluster", "ParameterValue": "nanoservices"}
]
aws cloudformation create-stack \
  --stack-name=greeting \
  --template-body=file://cfn.yaml \
  --parameters="$params"

Deploying a new version of the app

Deploying a new version of the app, e.g. from CI. Only ImageTag should change, all other parameters use previous value.

params="$(cfparams --template=cfn.yaml ImageTag=v2)"

Resulting JSON:

[
  {"ParameterKey": "Greeting", "UsePreviousValue": true},
  {"ParameterKey": "Recipient", "UsePreviousValue": true},
  {"ParameterKey": "ImageRepo", "UsePreviousValue": true},
  {"ParameterKey": "ImageTag", "ParameterValue": "v2"},
  {"ParameterKey": "Cluster", "UsePreviousValue": true}
]

Update stack:

aws cloudformation update-stack \
  --stack-name=greeting \
  --use-previous-template \
  --parameters="$params"

Updating the CloudFormation stack

Changing the stack, for example introducing a FooHost parameter.

 # cfn.yaml excerpt
 Parameters:
+  FooHost:
+    Type: String
+    Description: API key to access Foo service
   Greeting:
params="$(cfparams --template=cfn-foohost.yaml FooHost=foo.example.com)"

Resulting JSON:

[
  {"ParameterKey": "FooHost", "ParameterValue": "foo.example.com"},
  {"ParameterKey": "Greeting", "UsePreviousValue": true},
  {"ParameterKey": "Recipient", "UsePreviousValue": true},
  {"ParameterKey": "ImageRepo", "UsePreviousValue": true},
  {"ParameterKey": "ImageTag", "UsePreviousValue": true},
  {"ParameterKey": "Cluster", "UsePreviousValue": true}
]
name="greeting-update-$(date +%Y%m%d-%H%M%S)"

aws cloudformation create-change-set \
  --stack-name=greeting \
  --change-set-name="$name" \
  --use-previous-template \
  --parameters="$(cfparams --template=cfn.yaml FooHost=foo.example.com)"

# review Change Set here

aws cloudformation execute-change-set \
  --stack-name=greeting \
  --change-set-name="$name"

Introducing version-controlled parameters files

Now we introduce some version-controlled files to the subset of parameters that make sense to exist in the codebase. ImageTag is not included in this file.

# parameters-staging.yaml
FooHost: foo.local
Greeting: Howdy
Recipient: team
Cluster: staging
# parameters-production.yaml
FooHost: foo.example.com
Greeting: Hello
Recipient: world
Cluster: production
params="$(
  cfparams --template=cfn-foohost.yaml --parameters=parameters-staging.yaml \
    ImageTag=v3 Greeting=Bonjour
)

Resulting JSON:

[
  {"ParameterKey": "FooHost", "ParameterValue": "foo.local"},
  {"ParameterKey": "Greeting", "ParameterValue": "Howdy"},
  {"ParameterKey": "Recipient", "ParameterValue": "team"},
  {"ParameterKey": "ImageRepo", "UsePreviousValue": true},
  {"ParameterKey": "ImageTag", "ParameterValue": "v3"},
  {"ParameterKey": "Cluster", "ParameterValue": "staging"}
]

Bonus feature: Stack Tags

CloudFormation stacks can be tagged, and those tags flow into all taggable resources the stack creates. As with --parameters, the aws cloudformation commands expect these in an awkward format. cfparams --tags file.yaml helps.

aws cloudformation create-stack \
  ... \
  --tags "$(cfparams --tags=tags-production.yaml)" \
  ...
# tags-production.yaml
Name: Widgets as a Service
asset: widget-api
workload: production
cfparams --tags=tags-production.yaml
[
  {
    "Key": "Name",
    "Value": "Widgets as a Service"
  },
  {
    "Key": "asset",
    "Value": "widget-api"
  },
  {
    "Key": "workload",
    "Value": "production"
  }
]

Tags can also be overridden and added to on the command line, just like parameters can:

#tags-test.yaml
asset: buildkite
workload: delivery
buildkite-group: build-test
> cfparams --tags=tags-test.yaml buildkite-group=build-pylons message=extra-tag
[
  {
    "Key": "buildkite-group",
    "Value": "build-pylons"
  },
  {
    "Key": "message",
    "Value": "extra-tag"
  },
  {
    "Key": "asset",
    "Value": "buildkite"
  },
  {
    "Key": "workload",
    "Value": "delivery"
  }
]