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

Commit

Permalink
Merge 251e6df into 5487778
Browse files Browse the repository at this point in the history
  • Loading branch information
yufangzhang committed Sep 13, 2016
2 parents 5487778 + 251e6df commit 7c9e11e
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 21 deletions.
7 changes: 4 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ If your ``$CWD`` is anywhere else, you need to pass in a path to particular fabr
- **environment:dev** - The key name to read in the file specified to the ``config`` task
- **config:/path/to/file.yaml** - The location to the project YAML file
- **tag:test** - stack tag to differentiate between stacks
- **keyname:keyname** - the name of the keypair you uploaded in AWS which should store your SSH public key.

Multiple Stacks
===============

If you want to bring up a new stack as active stack, you will need to run the following fab tasks which we will explain later:
- **`fab-env cfn_create`:** create a new stack with a tag
- **`fab-env keyname:keyops tag:test cfn_create`:** create a new stack with a tag and keyname specified.
- **`fab-env salt.wait_for_minions`:** check if creation is done
- **`fab-env -u ubuntu update`:** install salt on the stack, add admins in keys.sls
- **`fab-env -u [your-ssh-name] update`:** remove `ubuntu`
- **`fab-env -i ~/.ssh/id_your_ssh_private_key -u ubuntu update`:** install salt on the stack, add admins from keys.sls
- **`fab-env -u [your-ssh-name] update`:** remove `ubuntu` user for security reason

Here `fab-env` refers to `fab application:courtfinder aws:prod environment:dev config:/path/to/courtfinder-dev.yaml passwords:/path/to/courfinder-dev-secrets.yaml`.

Expand Down
9 changes: 7 additions & 2 deletions bootstrap_cfn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def __init__(self,
# Overwrite user config with password config values
self.config[config_key] = utils.dict_merge(self.config[config_key],
passwords_config.get(config_key, {}))

except KeyError:
raise errors.BootstrapCfnError("Environment " + environment + " not found")

Expand Down Expand Up @@ -109,13 +110,14 @@ class ConfigParser(object):

config = {}

def __init__(self, data, stack_name, environment=None, application=None):
def __init__(self, data, stack_name, environment=None, application=None, keyname=None):
self.stack_name = stack_name
self.data = data
self.stack_id = self.stack_name.split('-')[-1]
# Some things possibly used in user data templates
self.environment = environment
self.application = application
self.keyname = keyname

def process(self):
template = self.base_template()
Expand Down Expand Up @@ -1085,7 +1087,7 @@ def ec2(self):

launch_config = LaunchConfiguration(
"BaseHostLaunchConfig",
KeyName=data['parameters']['KeyName'],
KeyName=self.get_keyname(),
SecurityGroups=[Ref(g) for g in sgs],
InstanceType=data['parameters']['InstanceType'],
AssociatePublicIpAddress=True,
Expand Down Expand Up @@ -1160,6 +1162,9 @@ def _find_resources(cls, template, resource_type):
def _get_elb_canonical_name(cls, elb_yaml_name):
return 'ELB-{}'.format(elb_yaml_name.replace('.', ''))

def get_keyname(self):
return self.keyname

def _attach_elbs(self, template):
if 'elb' not in self.data:
return template
Expand Down
2 changes: 0 additions & 2 deletions bootstrap_cfn/config_defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ prod:
max: 5
min: 1
parameters: &prod_ec2_parameters
KeyName: default
InstanceType: t2.micro
block_devices: &prod_ec2_block_devices
- DeviceName: /dev/sda1
Expand Down Expand Up @@ -34,7 +33,6 @@ dev:
max: 3
min: 1
parameters: &dev_ec2_parameters
KeyName: default
InstanceType: t2.micro
block_devices: &dev_ec2_block_devices
- DeviceName: /dev/sda1
Expand Down
38 changes: 35 additions & 3 deletions bootstrap_cfn/fab_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
env.setdefault('stack_passwords')
env.setdefault('blocking', True)
env.setdefault('aws_region', 'eu-west-1')
env.setdefault('keyname', None)

# GLOBAL VARIABLES
TIMEOUT = 3600
Expand Down Expand Up @@ -185,6 +186,18 @@ def user(username):
env.user = username


@task
def keyname(keyname):
"""
Sets the keyname to the keypair name on AWS
Sets the keyname to specific keypair name you created instead of "default"
Args:
keyname: the name of keypair on AWS
"""
env.keyname = str(keyname).lower()


@task
def swap_tags(tag1, tag2):
"""
Expand Down Expand Up @@ -495,9 +508,27 @@ def get_basic_config():
return project_config.config


def get_config():
def get_config(called_by_cfn_create=False):

Parser = env.get('cloudformation_parser', ConfigParser)
cfn_config = Parser(get_basic_config(), get_stack_name(), environment=env.environment, application=env.application)
basic_config = get_basic_config()
# keyname is mandatory in cfn_create, optional in others.
# keyname can be defined in fab or config while fab parameters has higher priority.
# otherwise not.
env_keyname = None
if called_by_cfn_create:
env_keyname = env.keyname
if env.keyname is None:
# if keyname is not defined in fab, check config file instead
print "[WARNING] keyname is not specified in fab command, checking config file..."
try:
env_keyname = basic_config['ec2']['parameters']['KeyName']
except KeyError:
sys.exit("[ERROR] KeyName is not defined in config file. "
"Please specify via fab e.g 'keyname:opskey' or config file")
print green("Creating stack with keyname: {0}").format(env_keyname)
cfn_config = Parser(get_basic_config(), get_stack_name(), environment=env.environment,
application=env.application, keyname=env_keyname)
return cfn_config


Expand Down Expand Up @@ -607,8 +638,9 @@ def cfn_create(test=False):
specification will be generated and used to create a
stack on AWS.
"""
_validate_fabric_env()
stack_name = get_stack_name(new=True)
cfn_config = get_config()
cfn_config = get_config(called_by_cfn_create=True)

cfn = get_connection(Cloudformation)
if test:
Expand Down
10 changes: 8 additions & 2 deletions tests/test_fab_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ def tail_logs(self, stack, stack_name):
print "{}.{} logs".format(stack, stack_name)
return True

@patch('bootstrap_cfn.fab_tasks._validate_fabric_env')
@patch('bootstrap_cfn.utils.get_events', return_value=[])
@patch('bootstrap_cfn.config.ConfigParser.process', return_value="test")
@patch('bootstrap_cfn.fab_tasks.get_cloudformation_tags', return_value="test")
Expand All @@ -323,7 +324,8 @@ def test_cfn_create_without_ssl(self, get_stack_name_function,
get_connection_function,
get_cloudformation_tags_function,
process_function,
get_events_function):
get_events_function,
_validate_fabric_env_function):
'''
create a stack without uploading ssl
Note: when testing creating stack, get_stack_name.return_value
Expand All @@ -344,10 +346,14 @@ def test_cfn_create_without_ssl(self, get_stack_name_function,
Returns:
'''
# this does not mock fabric env value actually.
# I just mock the whole function to do nothing for test simplicity.
_validate_fabric_env_function.side_effect = {"env.keyname.return_value": "default"}

get_connection_function.side_effect = self.connection_side_effect
basic_config_mock = yaml.load(set_up_basic_config())
get_config_function.return_value = config.ConfigParser(
basic_config_mock, "unittest_stack_name", "dev", "test")
basic_config_mock, "unittest_stack_name", "dev", "test", "default")
ret = fab_tasks.cfn_create(False)
self.assertTrue(ret)

Expand Down
25 changes: 16 additions & 9 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,8 @@ def test_elb_custom_sg(self):
compare(elb_dict['ELBtestdevexternal']['Properties']['SecurityGroups'],
[{u'Ref': u'SGName'}])

def test_cf_includes(self):
@patch('bootstrap_cfn.config.ConfigParser.get_keyname', return_value='default')
def test_cf_includes(self, get_keyname_mock):
project_config = ProjectConfig('tests/sample-project.yaml',
'dev',
'tests/sample-project-passwords.yaml')
Expand Down Expand Up @@ -853,7 +854,8 @@ def test_cf_includes(self):
outputs = cfg['Outputs']
compare(known_outputs, outputs)

def test_process(self):
@patch('bootstrap_cfn.config.ConfigParser.get_keyname', return_value='default')
def test_process(self, get_keyname_mock):
"""
This isn't the best test, but we at least check that we have the right
Resource names returned
Expand Down Expand Up @@ -910,7 +912,8 @@ def test_process(self):
}
compare(mappings, expected)

def test_process_with_vpc_config(self):
@patch('bootstrap_cfn.config.ConfigParser.get_keyname', return_value='default')
def test_process_with_vpc_config(self, get_keyname_mock):
"""
This isn't the best test, but we at least check that we have the right
Resource names returned
Expand Down Expand Up @@ -973,15 +976,17 @@ def test_process_with_vpc_config(self):
}
compare(mappings, expected)

def test_process_no_elbs_no_rds(self):
@patch('bootstrap_cfn.config.ConfigParser.get_keyname', return_value='default')
def test_process_no_elbs_no_rds(self, get_keyname_mock):
project_config = ProjectConfig('tests/sample-project.yaml', 'dev')
# Assuming there's no ELB defined
project_config.config.pop('elb')
project_config.config.pop('rds')
config = ConfigParser(project_config.config, 'my-stack-name')
config.process()

def test_ami_overrides_os_default(self):
@patch('bootstrap_cfn.config.ConfigParser.get_keyname', return_value='default')
def test_ami_overrides_os_default(self, get_keyname_mock):
self.maxDiff = None
project_config = ProjectConfig(
'tests/sample-project.yaml',
Expand Down Expand Up @@ -1402,7 +1407,8 @@ def test_elb_with_reserved_chars(self):
compare(self._resources_to_dict(known),
self._resources_to_dict(elb_cfg))

def test_ec2(self):
@patch('bootstrap_cfn.config.ConfigParser.get_keyname', return_value='default')
def test_ec2(self, get_keyname_mock):

self.maxDiff = None

Expand Down Expand Up @@ -1495,12 +1501,12 @@ def test_ec2(self):

compare(self._resources_to_dict(known), ec2_json)

@patch('bootstrap_cfn.config.ConfigParser.get_keyname', return_value='default')
# We just want to test that when we have userdata we return the right LaunchConfig.
def test_launchconfig_userdata(self):
def test_launchconfig_userdata(self, get_keyname_mock):
config = ConfigParser(
ProjectConfig('tests/sample-project.yaml', 'dev').config,
'my-stack-name')

BaseHostLaunchConfig = LaunchConfiguration(
"BaseHostLaunchConfig",
ImageId=FindInMap("AWSRegion2AMI", Ref("AWS::Region"), "AMI"),
Expand Down Expand Up @@ -1625,7 +1631,8 @@ def test_get_hostname_boothook_error(self):
config.get_hostname_boothook(cfg)
self.fail()

def test_ec2_with_no_block_device_specified(self):
@patch('bootstrap_cfn.config.ConfigParser.get_keyname', return_value='default')
def test_ec2_with_no_block_device_specified(self, get_keyname_mock):
project_config = ProjectConfig('tests/sample-project.yaml', 'dev')
project_config.config['ec2'].pop('block_devices')
config = ConfigParser(project_config.config, 'my-stack-name')
Expand Down

0 comments on commit 7c9e11e

Please sign in to comment.