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

Commit

Permalink
Add KeyName in fab parameters
Browse files Browse the repository at this point in the history
This commit adds keyname as a fab parameter.
with it, you can set it to your keypair in AWS
KeyName defined in config file will not be used.

keyname is mandatory fab parameter in cfn_create,
but not in others
  • Loading branch information
yufangzhang committed Aug 30, 2016
1 parent 88a3e0b commit 71f01eb
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 18 deletions.
7 changes: 4 additions & 3 deletions README.rst
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
8 changes: 6 additions & 2 deletions bootstrap_cfn/config.py
Expand Up @@ -109,13 +109,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 +1086,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 +1161,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
25 changes: 23 additions & 2 deletions bootstrap_cfn/fab_tasks.py
Expand Up @@ -35,6 +35,7 @@
env.setdefault('stack_passwords')
env.setdefault('blocking', True)
env.setdefault('aws_region', 'eu-west-1')
env.setdefault('keyname')

# 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 = keyname


@task
def swap_tags(tag1, tag2):
"""
Expand Down Expand Up @@ -466,7 +479,7 @@ def get_tag_record_name(stack_tag):
return record_name


def _validate_fabric_env():
def _validate_fabric_env(called_by_cfn_create=False):
if env.aws is None:
sys.exit("\n[ERROR] Please specify an AWS account, e.g 'aws:dev'")
if env.environment is None:
Expand All @@ -478,6 +491,12 @@ def _validate_fabric_env():
elif not os.path.isfile(env.config):
sys.exit("\n[ERROR] Config file %s does not exist" % str(env.config))

# keyname is mandatory in cfn_create, optional in others.
# if _validate_fabric_env() is called by cfn_create, then check keyname.
# otherwise not.
if called_by_cfn_create and env.keyname is None:
sys.exit("\n[ERROR] Please specify a keyname, e.g 'keyname:keyops'")

if env.stack_passwords is not None and not os.path.exists(env.stack_passwords):
print >> sys.stderr, "\n[ERROR] Passwords file '{0}' doesn't exist!".format(env.stack_passwords)
sys.exit(1)
Expand All @@ -497,7 +516,8 @@ def get_basic_config():

def get_config():
Parser = env.get('cloudformation_parser', ConfigParser)
cfn_config = Parser(get_basic_config(), get_stack_name(), environment=env.environment, application=env.application)
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,6 +627,7 @@ def cfn_create(test=False):
specification will be generated and used to create a
stack on AWS.
"""
_validate_fabric_env(called_by_cfn_create=True)
stack_name = get_stack_name(new=True)
cfn_config = get_config()

Expand Down
10 changes: 8 additions & 2 deletions tests/test_fab_tasks.py
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
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 71f01eb

Please sign in to comment.