cultureamp / cfparams

Wrangle parameters for AWS CloudFormation
MIT License
0 stars 0 forks source link

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

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
)"

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"
  }
]