Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Commit

Permalink
Merge 16d16da into 8b3241b
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmb committed Apr 13, 2015
2 parents 8b3241b + 16d16da commit 0ec2df1
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 20 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Unreleased

* Add include functionality for including extra cloudformation json files.

## Version 0.2.1

* Fix rsync missing passwords yaml from salt-pillar
Expand Down
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,12 @@ By default the ELBs will have a security group opening them to the world on 80 a
If you set the protocol on an ELB to HTTPS you must include a key called `certificate_name` in the ELB block (as example above) and matching cert data in a key with the same name as the cert under `ssl` (see example above). The `cert` and `key` are required and the `chain` is optional.

The certificate will be uploaded before the stack is created and removed after it is deleted.

Includes
++++++++++
If you wish to include some static cloudformation json and have it merged with the template generated by bootstrap-cfn. You can do the following in your template yaml file::

includes:
- /path/to/cloudformation.json

The tool will then merge this with the generated template *overwriting* any keys in the original template that clash.
26 changes: 7 additions & 19 deletions bootstrap_cfn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import sys
import yaml
import bootstrap_cfn.errors as errors
import bootstrap_cfn.utils as utils

from copy import deepcopy

class ProjectConfig:
Expand All @@ -14,31 +16,13 @@ def __init__(self, config, environment, passwords=None):
self.config = self.load_yaml(config)[environment]
if passwords:
passwords_dict = self.load_yaml(passwords)[environment]
self.config = self.dict_merge(self.config, passwords_dict)
self.config = utils.dict_merge(self.config, passwords_dict)

@staticmethod
def load_yaml(fp):
if os.path.exists(fp):
return yaml.load(open(fp).read())

def dict_merge(self, target, *args):
# Merge multiple dicts
if len(args) > 1:
for obj in args:
self.dict_merge(target, obj)
return target

# Recursively merge dicts and set non-dict values
obj = args[0]
if not isinstance(obj, dict):
return obj
for k, v in obj.iteritems():
if k in target and isinstance(target[k], dict):
self.dict_merge(target[k], v)
else:
target[k] = deepcopy(v)
return target


class ConfigParser:

Expand Down Expand Up @@ -92,6 +76,10 @@ def process(self):
template['Outputs'] = {}
for t in output_templates:
template['Outputs'].update(json.loads(pkgutil.get_data('bootstrap_cfn', t)))
if 'includes' in self.data:
for inc_path in self.data['includes']:
inc = json.load(open(inc_path))
utils.dict_merge(template, inc)
return json.dumps(
template, sort_keys=True, indent=4, separators=(',', ': '))

Expand Down
3 changes: 2 additions & 1 deletion bootstrap_cfn/salt_utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#!/usr/bin/env python
import sys
sys.path.append('/usr/local/bootstrap-cfn')
from bootstrap_cfn import utils
from bootstrap_cfn import errors
import salt
import salt.runner
import salt.client
import pprint
import time
import sys

def start_highstate(target):
local = salt.client.LocalClient()
Expand Down
37 changes: 37 additions & 0 deletions bootstrap_cfn/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import boto.exception
import boto.provider
import boto.sts
import sys
import time
import os

import bootstrap_cfn.errors as errors
from copy import deepcopy


def timeout(timeout, interval):
Expand All @@ -24,6 +27,22 @@ def wrapper(*args, **kwargs):

def connect_to_aws(module, instance):
try:
if instance.aws_profile_name == 'cross-account':
sts = boto.sts.connect_to_region(
region_name=instance.aws_region_name,
profile_name=instance.aws_profile_name
)
role = sts.assume_role(
role_arn=os.environ['AWS_ROLE_ARN_ID'],
role_session_name="AssumeRoleSession1"
)
conn = module.connect_to_region(
region_name=instance.aws_region_name,
aws_access_key_id=role.credentials.access_key,
aws_secret_access_key=role.credentials.secret_key,
security_token=role.credentials.session_token
)
return conn
conn = module.connect_to_region(
region_name=instance.aws_region_name,
profile_name=instance.aws_profile_name
Expand All @@ -33,3 +52,21 @@ def connect_to_aws(module, instance):
raise errors.NoCredentialsError()
except boto.provider.ProfileNotFoundError as e:
raise errors.ProfileNotFoundError(instance.aws_profile_name)

def dict_merge(target, *args):
# Merge multiple dicts
if len(args) > 1:
for obj in args:
self.dict_merge(target, obj)
return target

# Recursively merge dicts and set non-dict values
obj = args[0]
if not isinstance(obj, dict):
return obj
for k, v in obj.iteritems():
if k in target and isinstance(target[k], dict):
dict_merge(target[k], v)
else:
target[k] = deepcopy(v)
return target
9 changes: 9 additions & 0 deletions tests/sample-include.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Outputs":
{
"someoutput": {
"Description": "For tests",
"Value": "BLAHBLAH"
}
}
}
25 changes: 25 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from bootstrap_cfn.config import ProjectConfig, ConfigParser
import bootstrap_cfn.errors as errors
from testfixtures import compare
import json


class TestConfig(unittest.TestCase):
Expand Down Expand Up @@ -312,6 +313,30 @@ def test_elb_custom_sg(self):
compare(elb['Properties']['SecurityGroups'],
[{u'Ref': u'SGName'}])

def test_cf_includes(self):
project_config = ProjectConfig('tests/sample-project.yaml',
'dev',
'tests/sample-project-passwords.yaml')
project_config.config['includes'] = ['tests/sample-include.json']
known_outputs = {
"dbhost": {
"Description": "RDS Hostname",
"Value": {"Fn::GetAtt" : [ "RDSInstance" , "Endpoint.Address" ]}
},
"dbport": {
"Description": "RDS Port",
"Value": {"Fn::GetAtt" : [ "RDSInstance" , "Endpoint.Port" ]}
},
"someoutput":{
"Description": "For tests",
"Value": "BLAHBLAH"
}
}
config = ConfigParser(project_config.config, 'my-stack-name')
cfg = json.loads(config.process())
outputs = cfg['Outputs']
compare(known_outputs, outputs)

def test_process_no_elbs_no_rds(self):
project_config = ProjectConfig('tests/sample-project.yaml', 'dev')
# Assuming there's no ELB defined
Expand Down

0 comments on commit 0ec2df1

Please sign in to comment.