Configuration¶
Bespin is configured via a YAML file that contains Bespin configuration, environment specific configuration, and stack specific configuration.
Layout¶
The layout of your directory is configured by default to look something like:
root/
bespin.yml
<stack>.json
<stack2>.yaml
<environment1>/
<stack>-params.json
<stack2>-params.yaml
<environment2>/
<stack>-params.json
<stack2>-params.yaml
So say you have two stacks, one called app
and one called dns
, along with
only one environment called dev
:
root/
bespin.yml
app.json
dns.json
dev/
app-params.json
dns-params.json
and your bespin.yml would look something like:
---
environments:
dev:
account_id: 0123456789
vars:
variable1: value1
stacks:
app:
<options>
dns:
<options>
Where <options>
are the options for that stack.
Note
The location of the stack template file is configured by the
stack_json
or stack_yaml
option. The location of the params file is
configured by the params_json
or params_yaml
option. Alternatively
parameters can be specified inline (inside bespin.yml) using params_yaml
.
Bespin¶
assume_role = (optional) string
An iam role to assume into before doing any amazon requests.
The iam role can also be set via the ASSUME_ROLE
environment variable.
This behaviour can be disabled by setting the NO_ASSUME_ROLE
environment variable to any value.
chosen_artifact = (default=”“) string
chosen_stack = (default=”“) string
chosen_task = (default=”list_tasks”) string
config = file
configuration = any
dry_run = (default=False) boolean
environment = (optional) string
The environment in the configuration to use.
When a stack is created the stack configuration is merged with the configuration for this environment.
extra = (default=”“) string
extra_imports = [ [string, string] , … ]
flat = (default=False) boolean
Show
task to show the stacks as a flat list. Set by --flat
no_assume_role = (default=False) boolean
Stack¶
alerting_systems = (optional) { string : <options> }
endpoint = (required) string
name = “{_key_name_1}”
type = string_choice
verify_ssl = (default=True) boolean
artifact_retention_after_deployment = (default=False) boolean
artifacts = { string : <options> }
archive_format = (default=”tar”) string_choice
cleanup_prefix = (optional) string
commands = [ <options> , … ]
compression_type = string_choice
files = [ <options> , … ]
Any files to add into the artifact
For example:
files:
- content: "{__stack__.vars.version}"
path: /artifacts/app/VERSION.txt
history_length = integer
The number of artifacts to keep in s3
Note
These only get purged if the stack has artifact_retention_after_deployment
set
to true or if the clean_old_artifacts
task is run
not_created_here = (default=False) boolean
paths = [ [string, string] , … ]
upload_to = string
auto_scaling_group_name = (optional) string
bespin = any
build_after = [ string, … ]
build_env = [ [string, (string_or_int_as_string )] , … ]
build_first = [ string, … ]
build_timeout = (default=1200) integer
command = (optional) string
command_on_instances
task as the command to run on the instancesconfirm_deployment = (optional) <options>
auto_scaling_group_name = (optional) string
deploys_s3_path = (optional) [ [string, (integer )] , … ]
sns_confirmation = (optional) <options>
deployment_queue = (required) string
timeout = (default=300) integer
version_message = (required) string
url_checker = (optional) <options>
check_url = (required) string
endpoint = (required) delayed
expect = (required) string
timeout_after = (default=600) integer
zero_instances_is_ok = (default=False) boolean
dns = (optional) dns
downtimer_options = (optional) { valid_string(valid_alerting_system) : <options> }
hosts = [ string, … ]
env = [ [string, (string_or_int_as_string )] , … ]
environment = “{environment}”
ignore_deps = (default=False) boolean
key_name = “{_key_name_1}”
name = (default=”{_key_name_1}”) string
netscaler = (optional) <options>
configuration = (optional) { string : { string : netscaler_config } }
configuration_password = (optional) string
configuration_username = (optional) string
dry_run = to_boolean
host = (required) string
nitro_api_version = (default=”v1”) string
password = delayed
syncable_environments = (optional) [ valid_environment, … ]
username = (required) string
verify_ssl = (default=True) boolean
newrelic = (optional) <options>
account_id = (required) string
api_key = (required) string
application_id = (required) string
deployed_version = (required) string
env = [ [string, (string_or_int_as_string )] , … ]
notify_stackdriver = (default=False) boolean
params_json = valid_params_json
params_yaml = valid_params_yaml
role_name = string
scaling_options = <options>
highest_min = (default=2) integer
instance_count_limit = (default=10) integer
sensitive_params = (default=[‘Password’]) [ string, … ]
skip_update_if_equivalent = [ [delayed, delayed] , … ]
ssh = (optional) <options>
address = (optional) string
instance
is specifiedauto_scaling_group_name = (optional) string
bastion = (optional) string
bastion_key_location = (optional) string
bastion_key_path = (default=”{config_root}/{environment}/bastion_ssh_key.pem”) string
bastion_user = (required) string
instance = (optional) [ string, … ]
instance_key_location = (optional) string
instance_key_path = (default=”{config_root}/{environment}/ssh_key.pem”) string
storage_host = (optional) string
storage_type = (default=”url”) string_choice
user = (required) string
stack_json = valid_stack_json
stack_name = (default=”{_key_name_1}”) string
The name given to the deployed cloudformation stack
Note that this may include environment variables as defined by the stack_name_env
option:
stack_name: "rerun-{{RELEASE_VERSION}}"
stack_name_env:
- RELEASE_VERSION
stack_name_env = [ [string, (string_or_int_as_string )] , … ]
stack_policy = valid_policy_json
stack_yaml = valid_stack_yaml
stackdriver = (optional) <options>
api_key = (required) string
deployment_version = (default=”<version>”) string
suspend_actions = (default=False) boolean
Suspend Scheduled Actions for the stack before deploying, and resume Scheduled actions after finished deploying.
This uses the auto_scaling_group_name
attribute to determine what autoscaling group
to suspend and resume
tags = { valid_string(regex(^.{0,127}$)) : string }
A dictionary specifying the tags to apply to the stack
Cloudformation will apply these tags to all created resources
termination_protection = (default=False) boolean
vars = delayed
Environment¶
account_id = (required) ( valid_string(regex(\d+)) or integer)
region = (default=”ap-southeast-2”) string
tags = { valid_string(regex(^.{0,127}$)) : string }
A dictionary specifying the tags to apply to the stack
Cloudformation will apply these tags to all created resources
vars = dictionary
Password¶
bespin = “{bespin}”
crypto_text = (required) string
encryption_context = (optional) dictionary
grant_tokens = (optional) [ string, … ]
KMSMasterKey = (required) string
name = “{_key_name_1}”
vars = dictionary
Formatter¶
Configuration values may reference other parts of the config using ‘replacement
fields’ surrounded by curly braces {}
. Nested values can be referenced
using dot notation, eg: {foo.bar.quax}
.
If you need to include a brace character in the literal text, it can be escaped
by doubling: {{
and }}
.
Available fields:
- environment
- Current environment name as a string
- region
- Current environment’s region
- environments.<env_name>.*
Environment mappings.
Environment fields includes:
- account_id
- Environment AWS account id
- region
- Environment AWS region
- stacks.<stack_name>.*
- Stack mappings. See Stack spec for more detail.
- tags.*
- Tags mapping
- vars.*
- Vars mapping
Within a stack, bespin also defines the following aliases:
- __stack_name__
- Current stack name as a string.
- __stack__
- Current stack mapping (ie: stacks.__stack_name__). See Stack spec for more detail.
- __environment__
- Current environment mapping (ie: environments.environment).
In addition to configuration fields, bespin defines the following special values:
- config_root
- Directory of the main configuration file (ie:
dirname
of--bespin-config
) - :config_dir
(advanced) (python2.7+ or python3 required)
Directory of the configuration file where the value was defined. See
bespin.extra_files
.- _key_name_X
(advanced)
Refers to the key’s content X positions up from the current value, indexed from zero. For example, the following would result in “example vars test”:
stacks: test: vars: example: "{_key_name_0} {_key_name_1} {_key_name_2}"
Fields may also declare a formatter by suffixing the field with a colon :
and the name of the formatter to use.
Available formatters include:
- :env
- Formats environment variables suitable to be used in shell.
{USER:env}
would produce${USER}
. - :date
- Return a string representing the current datetime
(
datetime.datetime.now()
) formatted by strftime. See Python strftime for available format codes. eg:{%Y:date}
would result in the current year (eg: “2017”) - :underscored
- Converts ‘-‘ to ‘_’.
- :count
Returns the total number of elements in a list or CommaDelimitedList variable as a string.
The total number of elements in a CommaDelimitedList should be one more than the total number of commas. This implementation marries Cloudformation Parameters CommaDelimitedList’s implementation. Examples:
vars: one: "1" # {one:count} == "1" two: "1,2" # {two:count} == "2" three: "1,2,3" # {three:count} == "3" empty: "" # {empty:count} == "1" space: " " # {space:count} == "1" comma: "," # {comma:count} == "2"
Note
The formatter does not support nested values (eg: {a.{foo}.c}). See Stacks for details on using variable formatting (ie: XXX_MYVAR_XXX) instead.
Tasks¶
Bespin’s mechanism for doing anything are tasks. By default Bespin comes with a number of tasks as describe below:
Default tasks¶
- tail
- Tail the deployment of a stack
show
Show what stacks we have in layered order.
When combined with the
--flat
option, the stacks are shown as a flat list instead of in layers.
- deploy
- Deploy a particular stack
- params
- Print out the params
- become
- Print export statements for assuming an amazon iam role
- execute
- Exec a command using assumed credentials
- bastion
- SSH into the bastion
- outputs
- Print out the outputs
- downtime
- Downtime this stack in alerting systems
- instances
- Find and ssh into instances
- undowntime
- UnDowntime this stack in alerting systems
- list_tasks
- List the available_tasks
- deploy_plan
- Deploy a predefined list of stacks in order
- sanity_check
- Sanity check a stack and it’s dependencies
- num_instances
- Count the number of running instances.
- print_variable
- Prints out a variable from the stack
- scale_instances
- Change the number of instances in the stack’s auto_scaling_group
- encrypt_password
- Convert plain text password into crypto text
- publish_artifacts
- Build and publish an artifact
- sanity_check_plan
- sanity check a predefined list of stacks in order
- confirm_deployment
- Confirm deployment via SNS notification for each instance and/or url checks
- validate_templates
- Validates all stack templates and parameters against CloudFormation
- clean_old_artifacts
- Cleanup old artifacts
- wait_for_dns_switch
- Periodically check dns until all our sites point to where they should be pointing to for specified environment
- command_on_instances
- Run a shell command on all the instances in the stack
- sync_netscaler_config
- Sync netscaler configuration with the specified netscaler
- switch_dns_traffic_to
- Switch dns traffic to some environment
- create_stackdriver_event
- Create an event in stackdriver
- enable_server_in_netscaler
- Disable a server in the netscaler
- disable_server_in_netscaler
- Enable a server in the netscaler
- note_deployment_in_newrelic
- Note the deployment in newrelic
- resume_cloudformation_actions
- Resumes all schedule actions on a cloudformation stack
- suspend_cloudformation_actions
- Suspends all schedule actions on a cloudformation stack
Custom Tasks¶
There are two ways you can create custom tasks.
The first way is to define tasks
as part of a stack definition:
---
stacks:
app:
[..]
tasks:
deploy_app:
action: deploy
Will mean that you can run bespin deploy_app dev
and it will run the deploy
action for your app
stack.
Tasks have several options:
- action
- The task to run. Note that the stack will default to the stack you’ve defined this task on.
- options
- Extra options to merge into the stack configuration when running the task.
- overrides
- Extra options to merge into the root of the configuration when running the task.
- description
- A description that is shown for this task when you ask Bespin to list all the tasks.
The second way of defining custom tasks is with the extra_imports
option.
For example, let’s say you have the following layout:
bespin.yml
app.json
scripts.py
And your bespin.yml looked like:
---
bespin:
extra_imports:
- ["{config_root}", "scripts"]
stacks:
app:
[..]
Then before Bespin looks for tasks it will first import the python module named
scripts
that lives in the folder where bespin.yml
is defined. So in this
case, the scripts.py
.
The only thing scripts.py
needs is a __bespin__(bespin, task_maker)
method
where bespin
is the Bespin
object and task_maker
is a function that
may be used to register tasks.
For example:
def __bespin__(bespin, task_maker):
task_maker("deploy_app", "Deploy the app stack", action="deploy").specify_stack("app")
Here we have defined the deploy_app
action that will deploy
the app
stack.
We can do something more interesting if we also define a custom action:
from bespin.tasks import a_task
def __bespin__(bespin, task_maker):
task_maker("list_amis", "List amis with a particular tag")
@a_task(needs_credentials=True)
def list_amis(overview, configuration, **kwargs):
credentials = configuration['bespin'].credentials
amis = credentials.ec2.get_all_images(filters={"tag:application": "MyCreatedAmis"})
for ami in amis:
print(ami.id)
And then we can do bespin list_amis dev
and it will find all the Amis that have
an application
tag with MyCreatedAmis
.
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
.
Deployment¶
Bespin offers the ability to deploy stacks, taking into account dependency resolution and deployment checking.
For example, let’s say we have the following configuration:
---
environment:
dev:
account_id: "12345789"
stacks:
security_groups:
stack_name: appplication_security_groups
app:
stack_name: application
vars:
app_security_groups: ["{stacks.security_groups}", "AppSecurityGroup"]
params_yaml:
AppSecurityGroup: XXX_APP_SECURITY_GROUP_XXX
build_after:
- dns
dns:
stack_name: application-dns
And we do bespin deploy dev app
, then it will first deploy security_groups
,
use the output from that stack as a variable for the parameters for the app
stack, which gets deployed next. After the app stack is deployed, the dns
stack
will then be deployed (because of the build_after
option).
Plans¶
You can explicitly specify an order of stacks by creating a plan
:
---
environments:
dev:
account_id: "12345678"
plan:
all:
- vpc
- gateways
- subnets
- subnet_rules
- nat
- dns
- dhcp
- dns_names
- peering
stacks:
vpc:
[..]
gateways:
[..]
[..etc..]
And then you may deploy that plan with bespin deploy_plan dev all
Confirming deployment¶
It’s useful to be able to confirm that a deployment was actually successful even if the cloudformation successfully deployed:
---
environments:
dev:
account_id: "123456789"
stacks:
app:
stack_name: application
env:
- BUILD_NUMBER
params_yaml:
BuildNumber: XXX_BUILD_NUMBER_XXX
confirm_deployment:
url_checker:
expect: "{{BUILD_NUMBER}}"
endpoint: ["{stacks.app}", PublicEndpoint]
check_url: /diagnostic/version
timeout_after: 600
In this example, the deployment is checked by checking that a url returns some
expected value. In this case it expects the url /diagnostic/version
to return
the BUILD_NUMBER we deployed with.
Confirm_deployment has multiple options
- url_checker
- As per the example above, this checks a url on our app returns a particular value
- sns_confirmation:
This confirms that an sqs topic receives a particular message:
confirm_deployment: auto_scaling_group_name: AppServerAutoScalingGroup sns_confirmation: timeout: 300 version_message: "{{BUILD_NUMBER}}" deployment_queue: deployment-queue
This configuration will expect that the sqs queue called
deployment-queue
will receive a message for each new instance in the auto scaling group saying<instance_id>:success:<version_message>
Actually sending these messages is up to the definition of the cloudformation stack.
Note
The naming of this is the result of an implementation detail where this was first implemented for a stack that populated the sqs queue via an sns notification.
- deploys_s3_path:
This allows you to specify an s3 path that you expect to have a value with a modified time newer than the deployment of the stack:
confirm_deployment: deploys_s3_path: - ["s3://my-bucket/generated/thing.tar.gz", 600]
Where the number is the timeout of looking for this s3 path.
When zero instances is ok¶
In some environments it may be ok that a stack deploys and has no instances
associated with it. In this case you may set the zero_instances_is_ok: true
.
If this isn’t set and no instances are in the autoscaling group after the stack is deployed, then Bespin will complain saying the deployment failed to make any instances:
---
environments:
dev:
account_id: "123456789"
prod:
account_id: "123456789"
stacks:
app:
stack_name: my-application
confirm_deployment:
auto_scaling_group_name: AppServerAutoScalingGroup
url_checker:
endpoint: endpoint.my-company.com
expects: success
check_url: /diagnostic/status
# Add zero_instances_is_ok just for the dev environment
environments:
dev:
confirm_deployment:
zero_instances_is_ok: true
Artifacts¶
Bespin lets you define, create and upload artifacts as defined in the configuration. Where an artifact is just an archive of files either generated or taken from the filesystem.
Artifacts are defined per stack:
---
environments:
dev:
account_id: "123456789"
stacks:
app:
artifacts:
main:
compression_type: gz
upload_to: s3://my-bucket/artifacts/main.tar.gz
paths:
- ["{config_root}/ansible", "/ansible"]
With this example, bespin publish_artifacts dev app
will create an archive of
an ansible
folder next to the configuration, which is uploaded to
s3://my-bucket/artifacts/main.tar.gz
.
Specifying the contents¶
There are currently a few ways of specifying the contents of the archive:
- paths
- As in the example above, paths is a list of lists. Each item in the list being
[<local_location>, <location_in_archive>]
and will take from the local location and put into the archive under the location that is specified. - files
Allows you to add files into the archive. For example:
files: - content: | A file with content goes here dest: /location/in/archive.txt
This creates a file at
/location/in/archive.txt
with the content as specified.You can also generate the content from a custom task. So say you’ve defined a custom task called
generate_ansible_playbook
then you can specify:files: - task: generate_ansible_playbook dest: /ansible/playbook.yml
- commands
This one lets you copy files from your disk into some temporary location, edit any files as you see fit, run an arbitrary command in the temporary location and add files from there into the archive:
commands: - copy: - ["{config_root}/../../play-app", "/"] modify: "conf/application.conf": append: - 'app_version="{__stack__.vars.version}"' command: "sbt dist" add_into_tar: - ["target/universal/{vars.app_name}-SNAPSHOT.zip", "/artifacts/{vars.app_name}.zip"]
Here we’ve copied our play-app into the root of the temporary location, added the version to the
application.conf
, runsbt dist
in the temporary location, and then added the resulting file into the archive under/artifacts/<app_name>.zip
Environment Variables¶
It’s useful to be able to pass in environment variables, like the build number
and then use it. This is done with build_env
, which acts like env
For example:
---
environments:
dev:
account_id: "123456789"
stacks:
app:
build_env:
- BUILD_NUMBER
- GIT_COMMIT
vars:
version: "{{BUILD_NUMBER}}-{{GIT_COMMIT}}"
artifacts:
main:
upload_to: "s3://my-bucket/artifacts/app-{{BUILD_NUMBER}}.tar.gz"
files:
- content: {__stack__.vars.version}
dest: /artifacts/version.txt
paths:
- ["{config_root}/ansible", /ansible]
Note that referring to environment variables is done with “{{<variable>}}”. This is because bespin formats the string twice, once with the configuration, and a second time with the environment variables.
Cleaning up artifacts¶
It’s dangerous to clean up artifacts with a time based policy in S3 because if you don’t create new artifacts for a long enough amount of time, then s3 will clean up an artifact that is used by production and so when new machines come up there won’t be an artifact.
Instead, it is better to manually clean up artifacts and keep a certain number of previous artifacts.
Bespin helps this with the clean_old_artifacts
task:
---
environments:
dev:
account_id: "123456789"
stacks:
app:
build_env:
- BUILD_NUMBER
artifacts:
main:
history_length: 5
cleanup_prefix: app-
compression_type: gz
upload_to: "s3://my-bucket/artifacts/app-{{BUILD_NUMBER}}.tar.gz"
paths:
- ["{config_root}/ansible", /ansible]
With this configuration, bespin clean_old_artifacts dev app
will find all
the artifacts under s3://my-bucket/artifacts
with the prefix app-
, keep
the newest 5
and delete the rest.
Note
If you just want to use the clean_old_artifacts logic but your artifacts
are generated and uploaded by something else, then specify not_created_here: true
SSH’ing into instances¶
It’s useful to be able to ssh into instances that your bring up in your stack.
Note
bespin uses RadSSH which honours ssh_config(5)
(ie: ~/.ssh/config).
Users may want to set StrictHostKeyChecking no
to ignore hostkeys and/or
UserKnownHostsFile /dev/null
to prevent host key additions for
dynamic/cloud instances.
Bespin provides the instances
command for finding the instances, getting the
ssh key, and ssh’ing into one of the instances.
This command also handles going via a jumphost/bastion instance.
---
environments:
dev:
account_id: "123456789"
stacks:
app:
stack_name: my_application
ssh:
bastion_host: bastion.my_company.com
bastion_user: ec2-user
bastion_key_path: "{config_root}/{environment}/bastion_ssh_key.pem"
user: ec2-user
auto_scaling_group_name: AppServerAutoScalingGroup
instance_key_path: "{config_root}/{environment}/ssh_key.pem
With this configuration, bespin instances dev app
will look for all the
instances in the AppServerAutoScalingGroup
defined by the my_application
cloudformation stack and list the ips:
$ bespin instances dev app
Found 1 instances
====================
i-d848ca04 10.35.3.151 running Up 9990 seconds
Then you can run bespin instances dev app 10.35.3.151
and with this configuration
will ssh through ec2-user@bastion.my_company.com
into ec2-user@10.35.3.151
.
If the bastion options are not specified, then no bastion is used.
Fetching ssh keys from Rattic¶
Bespin offers the ability to fetch ssh keys stored in Rattic:
---
environments:
dev:
account_id: "123456789"
stacks:
app:
stack_name: my_application
ssh:
bastion_host: bastion.my_company.com
bastion_user: ec2-user
bastion_key_path: "{config_root}/{environment}/bastion_ssh_key.pem"
bastion_key_location: "2200"
user:ec2-user
auto_scaling_group_name: Appserverautoscalinggroup
instance_key_location: "2201"
storage_type: rattic
storage_host: rattic.my_company.com
instance_key_path: "{config_root}/{environment}/ssh_key.pem
With this configuration, if bespin can’t find the ssh key specified by
bastion_key_path
and instance_key_path
then it will get the ssh keys
from rattic.my_company.com
using the key ids specified by bastion_key_location
and instance_key_location
.
Note that the ssh keys must be uploaded to rattic as ssh keys, not as attachments.
Note
The instance_key_path and bastion_key_path in these two examples are the same as the defaults, so leaving them out would have the same effect.
Specifying hosts¶
The hosts can be found by either specifying auto_scaling_group_name
which
will look for all the instances attached to that scaling group, or by specifying
instance
which will look for that instance as specified in the cloudformation
stack.
For example, if my stack.json has this in it:
{ "Resources":
{ "MyInstance":
{ "Type": "AWS::EC2::Instance"
, "Properties": [..]
}
}
}
Then I can specify it by having:
ssh:
user: ec2-user
instance: MyInstance
When you do this you may also specify an address that is displayed instead of an ip address:
ssh:
user: ec2-user
instance: BastionHost
address: bastion.{environment}.my-company.com
So you’d get something like:
$ bespin instances dev app
Found 1 instances
====================
i-d848ca04 bastion.dev.my-company.com running Up 9001 seconds
$ bespin instances prod app
Found 1 instances
====================
i-f849ca94 bastion.prod.my-company.com running Up 9001 seconds
Project Dormant¶
This project was purpose built for a need at a previous workplace of mine.
As far as I could tell, that was the only place this was used and since my departure from that workplace they have slowly switched to other projects for their deployments.
I don’t use AWS at my current workplace, or in any personal projects and so haven’t needed to use this.
In the future it’s likely I’ll only have time for small changes in this project.
Bespin¶
An opinionated wrapper around Amazon Cloudformation that reads yaml files. and make things happen.

The documentation can be found at http://bespin.readthedocs.io
Usage¶
Once bespin is installed, there will be a new program called bespin
.
When you call bespin without any arguments it will print out the tasks you have available.
You may invoke these tasks with the task
option.
Simpler Usage¶
To save typing --task
, --stack
and --environment
too much
, the first positional argument is treated as task
(unless it is prefixed with a -
); the second positional argument
(if also not prefixed with a -
) is taken as the environment
and the third is
treated as the stack
.
So:
$ bespin --task deploy --environment dev --stack app
Is equivalent to:
$ bespin deploy dev app
Logging colors¶
If you find the logging output doesn’t look great on your terminal, you can
try setting the term_colors
option in bespin.yml
to either light
or
dark
.
The yaml configuration¶
Bespin reads everything from a yaml configuration. By default this is a
bespin.yml
file in the current directory, but may be changed with the
--bespin-config
option or BESPIN_CONFIG
environment variable.
It will also read from ~/.bespin.yml
and will be overridden by anything in
the configuration file you’ve specified.
Tests¶
Install testing deps and run the helpful script:
pip install -e .
pip install -e ".[tests]"
./test.sh