Skip to content

Commit

Permalink
Merge pull request #46 from mhahn/subcommands-documentation
Browse files Browse the repository at this point in the history
Subcommands documentation
  • Loading branch information
phobologic committed Aug 6, 2015
2 parents 0be8272 + 147c81b commit 2ce98b2
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 95 deletions.
40 changes: 33 additions & 7 deletions stacker/actions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ def stack_template_url(self, blueprint):
return stack_template_url(self.bucket_name, blueprint)

def s3_stack_push(self, blueprint, force=False):
""" Pushes the rendered blueprint's template to S3.
"""Pushes the rendered blueprint's template to S3.
Verifies that the template doesn't already exist in S3 before
pushing.
Returns the URL to the template in S3.
"""
key_name = stack_template_key_name(blueprint)
template_url = self.stack_template_url(blueprint)
Expand Down Expand Up @@ -86,16 +87,41 @@ def run(self, *args, **kwargs):
def post_run(self, *args, **kwargs):
pass

def _get_all_stack_names(self, dependency_dict):
def _get_all_stack_names(self, dependencies):
"""Get all stack names specified in dependencies.
Args:
- dependencies (dict): a dictionary where each key should be the
fully qualified name of a stack whose value is an array of
fully qualified stack names that the stack depends on.
Returns:
set: set of all stack names
"""
return set(
dependency_dict.keys() +
[item for dependencies in dependency_dict.values() for item in dependencies]
dependencies.keys() +
[item for items in dependencies.values() for item in items]
)

def get_stack_execution_order(self, dependency_dict):
# copy the dependency_dict since we pop items out of it to get the
def get_stack_execution_order(self, dependencies):
"""Return the order in which the stacks should be executed.
Args:
- dependencies (dict): a dictionary where each key should be the
fully qualified name of a stack whose value is an array of
fully qualified stack names that the stack depends on. This is
used to generate the order in which the stacks should be
executed.
Returns:
array: An array of stack names in the order which they should be
executed.
"""
# copy the dependencies since we pop items out of it to get the
# execution order, we don't want to mutate the one passed in
dependencies = copy.deepcopy(dependency_dict)
dependencies = copy.deepcopy(dependencies)
pending_steps = []
executed_steps = []
stack_names = self._get_all_stack_names(dependencies)
Expand Down
59 changes: 31 additions & 28 deletions stacker/actions/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,38 @@


class Action(BaseAction):
""" Responsible for building & coordinating CloudFormation stacks.
"""Responsible for building & coordinating CloudFormation stacks.
Handles the conversion from:
config -> Blueprints -> Cloudformation Templates
Generates the build plan based on stack dependencies (these dependencies
are determined automatically based on references to output values from
other stacks).
Then pushes the templates into S3 if they have changed. Then kicks off
the stacks in order, depending on their dependencies/requirements (to
other stacks, and usually it is done automatically though manual
dependencies can be specified in the config).
The plan can then either be printed out as an outline or executed. If
executed, each stack will get launched in order which entails:
- Pushing the generated CloudFormation template to S3 if it has chnaged
- Submitting either a build or update of the given stack to the `Provider`.
- Stores the stack outputs for reference by other stacks.
If a stack already exists, but it's template or parameters have changed
it updates the stack, handling dependencies.
Also manages the translation of Output's to Parameters between stacks,
allowing you to pull information from one stack and use it in another.
"""

def _resolve_parameters(self, outputs, parameters, blueprint):
""" Resolves parameters for a given blueprint.
"""Resolves parameters for a given blueprint.
Given a list of parameters, first discard any parameters that the
blueprint does not use. Then, if a remaining parameter is in the format
<stack_name>::<output_name>, pull that output from the foreign
stack.
Args:
outputs (dict): any outputs that can be referenced by other stacks
parameters (dict): A dictionary of parameters provided by the
stack definition
blueprint (Blueprint): A stacker.blueprint.base.Blueprint object
that is having the parameters applied to
it.
stack definition
blueprint (`stacker.blueprint.base.Blueprint`): A Blueprint object
that is having the parameters applied to it.
Returns:
dict: The resolved parameters.
"""
params = {}
blueprint_params = blueprint.parameters
Expand All @@ -69,8 +67,7 @@ def _resolve_parameters(self, outputs, parameters, blueprint):
return params

def _build_stack_tags(self, stack, template_url):
""" Builds a common set of tags to attach to a stack.
"""
"""Builds a common set of tags to attach to a stack"""
requires = [req for req in stack.requires]
logger.debug("Stack %s required stacks: %s",
stack.name, requires)
Expand All @@ -83,10 +80,11 @@ def _build_stack_tags(self, stack, template_url):
return tags

def _launch_stack(self, results, stack, **kwargs):
""" Handles the creating or updating of a stack in CloudFormation.
"""Handles the creating or updating of a stack in CloudFormation.
Also makes sure that we don't try to create or update a stack while
it is already updating or creating.
"""
provider_stack = self.provider.get_stack(stack.fqn)
if provider_stack and kwargs.get('status') is SUBMITTED:
Expand Down Expand Up @@ -119,9 +117,10 @@ def _launch_stack(self, results, stack, **kwargs):
return SUBMITTED

def _get_outputs(self, stack):
""" Gets all the outputs from a given stack in CloudFormation.
"""Gets all the outputs from a given stack in CloudFormation.
Updates the local output cache with the values it finds.
"""
provider_stack = self.provider.get_stack(stack.fqn)
if not provider_stack:
Expand All @@ -133,24 +132,25 @@ def _get_outputs(self, stack):
return stack_outputs

def _handle_missing_parameters(self, params, required_params, existing_stack=None):
""" Handles any missing parameters.
"""Handles any missing parameters.
If an existing_stack is provided, look up missing parameters there.
Args:
params (dict): key/value dictionary of stack definition parameters
required_params (list): A list of required parameter names.
existing_stack (Stack): A boto.cloudformation.stack.Stack object.
If provided, will be searched for any
missing parameters.
existing_stack (`boto.cloudformation.stack.Stack`): A `Stack`
object. If provided, will be searched for any missing
parameters.
Returns:
list of tuples: The final list of key/value pairs returned as a
list of tuples.
list of tuples.
Raises:
MissingParameterException: Raised if a required parameter is
still missing.
still missing.
"""
missing_params = list(set(required_params) - set(params.keys()))
if existing_stack:
Expand All @@ -168,7 +168,7 @@ def _handle_missing_parameters(self, params, required_params, existing_stack=Non
return params.items()

def _generate_plan(self):
plan = Plan(details='Create/Update stacks', provider=self.provider)
plan = Plan(description='Create/Update stacks')
stacks = self.context.get_stacks_dict()
dependencies = self._get_dependencies()
for stack_name in self.get_stack_execution_order(dependencies):
Expand All @@ -188,6 +188,7 @@ def _get_dependencies(self):
return dependencies

def pre_run(self, outline=False, *args, **kwargs):
"""Any steps that need to be taken prior to running the action."""
pre_build = self.context.config.get('pre_build')
if not outline and pre_build:
util.handle_hooks('pre_build', pre_build, self.provider.region, self.context)
Expand All @@ -196,6 +197,7 @@ def run(self, outline=False, *args, **kwargs):
"""Kicks off the build/update of the stacks in the stack_definitions.
This is the main entry point for the Builder.
"""
plan = self._generate_plan()
if not outline:
Expand All @@ -209,6 +211,7 @@ def run(self, outline=False, *args, **kwargs):
plan.outline(execute_helper=True)

def post_run(self, outline=False, *args, **kwargs):
"""Any steps that need to be taken after running the action."""
post_build = self.context.config.get('post_build')
if not outline and post_build:
util.handle_hooks('post_build', post_build, self.context)
18 changes: 8 additions & 10 deletions stacker/blueprints/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@
class Blueprint(object):
"""Base implementation for dealing with a troposphere template.
:type name: string
:param name: A name for the blueprint. If not provided, one
will be created from the class name automatically.
:type parameters: Dictionary
:param stack: Used for configuring the Blueprint.
:type mappings: dict
:param mappings: Cloudformation Mappings to be used in the template.
Args:
name (str): A name for the blueprint. If not provided, one will be
created from the class name automatically.
context (`stacker.context.Context`): the context the blueprint is being
executed under.
mappings (Optional[dict]): Cloudformation Mappings to be used in the
template.
"""
def __init__(self, name, context, mappings=None):
Expand All @@ -33,7 +31,7 @@ def parameters(self):

@property
def required_parameters(self):
""" Returns all template parameters that do not have a default value. """
"""Returns all template parameters that do not have a default value."""
required = []
for k, v in self.parameters.items():
if not hasattr(v, 'Default'):
Expand Down
4 changes: 0 additions & 4 deletions stacker/commands/stacker/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# TODO add a better description about all the actions
"""Description about what stacker does
"""
import copy

from .build import Build
Expand All @@ -12,7 +9,6 @@
class Stacker(BaseCommand):

name = 'stacker'
description = __doc__
subcommands = (Build,)

def configure(self, options, **kwargs):
Expand Down
9 changes: 4 additions & 5 deletions stacker/commands/stacker/build.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# TODO make sure this is still relevant
"""Launches or updates cloudformation stacks based on the given config.
"""Launches or updates CloudFormation stacks based on the given config.
The script is smart enough to figure out if anything (the template, or
parameters) has changed for a given stack. If not, it will skip that stack.
Stacker is smart enough to figure out if anything (the template or parameters)
have changed for a given stack. If nothing has changed, stacker will correctly
skip executing anything against the stack.
Can also pull parameters from other stack's outputs.
"""

from .base import StackerCommand
Expand Down
17 changes: 14 additions & 3 deletions stacker/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,20 @@
from . import exceptions


def parse_config(config_string, environment=None):
""" Parse a config, using it as a template with the environment. """
t = Template(config_string)
def parse_config(raw_config, environment=None):
"""Parse a config, using it as a template with the environment.
Args:
raw_config (str): the raw stacker configuration string.
environment (Optional[dict]): any environment values that should be
passed to the config
Returns:
dict: the stacker configuration populated with any values passed from
the environment
"""
t = Template(raw_config)
buff = StringIO()
if not environment:
environment = {}
Expand Down
19 changes: 16 additions & 3 deletions stacker/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@


class Context(object):
"""The context under which the current stacks are being executed.
The stacker Context is responsible for translating the values passed in via
the command line and specified in the config to `Stack` objects.
"""

_optional_keys = ('environment', 'stack_names', 'parameters', 'mappings', 'config')

Expand All @@ -22,10 +28,16 @@ def _get_stack_definitions(self):
return [s for s in self.config['stacks'] if s['name'] in self.stack_names]

def get_stacks(self):
# TODO fix docstring
"""Extract stack definitions from the config.
"""Get the stacks for the current action.
Handles configuring the `stacker.stack.Stack` objects that will be used
in the current action. Responsible for merging the stack definition in
the config, the parameters specified on the command line, and any
mappings specified in the config.
Returns:
list: a list of `stacker.stack.Stack` objects
If no stack_list given, return stack config as is.
"""
if not hasattr(self, '_stacks'):
definitions = self._get_stack_definitions()
Expand All @@ -44,4 +56,5 @@ def get_stacks_dict(self):
return dict((stack.fqn, stack) for stack in self.get_stacks())

def get_fqn(self, name=None):
"""Return the fully qualified name of an object within this context."""
return '-'.join(filter(None, [self._base_fqn, name]))
2 changes: 2 additions & 0 deletions stacker/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@


class StackDoesNotExist(Exception):

def __init__(self, stack_name, *args, **kwargs):
Expand Down
3 changes: 2 additions & 1 deletion stacker/hooks/ecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ def connect_to_region(region_name, **kw_params):


def create_clusters(region, namespace, mappings, parameters, **kwargs):
""" Creates ECS clusters.
"""Creates ECS clusters.
Expects a 'clusters' argument, which should contain a list of cluster
names to create.
"""
conn = connect_to_region(region)
try:
Expand Down
3 changes: 2 additions & 1 deletion stacker/hooks/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@

def create_ecs_service_role(region, namespace, mappings, parameters,
**kwargs):
""" Used to create the ecsServieRole, which has to be named exactly that
"""Used to create the ecsServieRole, which has to be named exactly that
currently, so cannot be created via CloudFormation. See:
http://docs.aws.amazon.com/AmazonECS/latest/developerguide/IAM_policies.html#service_IAM_role
"""
conn = ConnectionManager(region).iam
policy = Policy(
Expand Down

0 comments on commit 2ce98b2

Please sign in to comment.