Skip to content

Commit

Permalink
Add a step to support Cloudformation in CodePipeline (#9)
Browse files Browse the repository at this point in the history
Add a step to support Cloudformation in CodePipeline
  • Loading branch information
john-shaskin authored and brettswift committed Sep 11, 2018
1 parent 26afa5a commit 669033e
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 2 deletions.
58 changes: 58 additions & 0 deletions cumulus/policies/cloudformation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import awacs
import awacs.aws
import awacs.logs
import awacs.iam
import awacs.s3
import awacs.ecr

from troposphere import iam


def get_policy_cloudformation_general_access(policy_name):
# TODO: Return policy with permissions:
# 1. Full Cloudformation access to stacks prefixed with application name
# 2. IAM access (currently using unlimited access, but this seems like it could be limited a bit)
return iam.Policy(
PolicyName=policy_name,
PolicyDocument=awacs.aws.PolicyDocument(
Version="2012-10-17",
Id="%sId" % policy_name,
Statement=[
awacs.aws.Statement(
Effect=awacs.aws.Allow,
Action=[
awacs.aws.Action("cloudformation", "*"),
awacs.aws.Action("ec2", "*"),
awacs.aws.Action("route53", "*"),
awacs.aws.Action("iam", "*"),
awacs.aws.Action("elasticloadbalancing", "*"),
awacs.aws.Action("s3", "*"),
awacs.aws.Action("autoscaling", "*"),
awacs.aws.Action("apigateway", "*"),
awacs.aws.Action("cloudwatch", "*"),
awacs.aws.Action("cloudfront", "*"),
awacs.aws.Action("rds", "*"),
awacs.aws.Action("dynamodb", "*"),
awacs.aws.Action("lambda", "*"),
awacs.aws.Action("sqs", "*"),
awacs.aws.Action("events", "*"),
awacs.ecr.GetDownloadUrlForLayer,
awacs.ecr.BatchGetImage,
awacs.ecr.BatchCheckLayerAvailability,
awacs.iam.PassRole,
],
Resource=["*"]
),
awacs.aws.Statement(
Effect=awacs.aws.Allow,
Action=[
awacs.logs.CreateLogGroup,
awacs.logs.CreateLogStream,
awacs.logs.PutLogEvents,
],
# TODO: restrict more accurately
Resource=["*"]
)
]
)
)
89 changes: 89 additions & 0 deletions cumulus/steps/dev_tools/cloud_formation_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import awacs

import cumulus.policies
import cumulus.policies.cloudformation
import cumulus.types.codebuild.buildaction

from troposphere import iam, codepipeline, GetAtt
from cumulus.chain import step
from cumulus.steps.dev_tools import META_PIPELINE_BUCKET_POLICY_REF


class CloudFormationAction(step.Step):

def __init__(self,
action_name,
input_artifact_name,
input_template_file,
input_configuration_file,
stage_name_to_add,
stack_name,
action_mode):
"""
:type action_name: basestring Displayed on the console
"""
step.Step.__init__(self)
self.action_name = action_name
self.input_artifact_name = input_artifact_name
self.input_template_file = input_template_file
self.input_configuration_file = input_configuration_file
self.stage_name_to_add = stage_name_to_add
self.stack_name = stack_name
self.action_mode = action_mode

def handle(self, chain_context):

print("Adding action %sstage" % self.action_name)

policy_name = "CloudFormationPolicy%stage" % chain_context.instance_name
role_name = "CloudFormationRole%stage" % self.action_name

cloud_formation_role = iam.Role(
role_name,
Path="/",
AssumeRolePolicyDocument=awacs.aws.Policy(
Statement=[
awacs.aws.Statement(
Effect=awacs.aws.Allow,
Action=[awacs.sts.AssumeRole],
Principal=awacs.aws.Principal(
'Service',
["cloudformation.amazonaws.com"]
)
)]
),
Policies=[
cumulus.policies.cloudformation.get_policy_cloudformation_general_access(policy_name)
],
ManagedPolicyArns=[
chain_context.metadata[META_PIPELINE_BUCKET_POLICY_REF]
]
)

cloud_formation_action = cumulus.types.codebuild.buildaction.CloudFormationAction(
Name=self.action_name,
InputArtifacts=[
codepipeline.InputArtifacts(Name=self.input_artifact_name)
],
Configuration={
'ActionMode': self.action_mode.value,
'RoleArn': GetAtt(cloud_formation_role, 'Arn'),
'StackName': self.stack_name,
'Capabilities': 'CAPABILITY_NAMED_IAM',
'TemplateConfiguration': self.input_artifact_name + "::" + self.input_configuration_file,
'TemplatePath': self.input_artifact_name + '::' + self.input_template_file
},
RunOrder="1"
)

chain_context.template.add_resource(cloud_formation_role)

stage = cumulus.util.tropo.TemplateQuery.get_pipeline_stage_by_name(
template=chain_context.template,
stage_name=self.stage_name_to_add
)

# TODO accept a parallel action to the previous action, and don't +1 here.
next_run_order = len(stage.Actions) + 1
cloud_formation_action.RunOrder = next_run_order
stage.Actions.append(cloud_formation_action)
10 changes: 8 additions & 2 deletions cumulus/steps/dev_tools/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ def handle(self, chain_context):
],
Resource=["*"]
),
awacs.aws.Statement(
Effect=awacs.aws.Allow,
Action=[
awacs.iam.PassRole
],
Resource=["*"]
),
],
)
)
Expand All @@ -178,10 +185,9 @@ def handle(self, chain_context):
]
)


generic_pipeline = codepipeline.Pipeline(
"Pipeline",
Name=chain_context.instance_name,
# Name=chain_context.instance_name,
RoleArn=troposphere.GetAtt(pipeline_service_role, "Arn"),
Stages=[],
ArtifactStore=codepipeline.ArtifactStore(
Expand Down
Empty file.
9 changes: 9 additions & 0 deletions cumulus/types/cloudformation/action_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from enum import Enum


class ActionMode(Enum):
CHANGE_SET_EXECUTE = "CHANGE_SET_EXECUTE"
CHANGE_SET_REPLACE = "CHANGE_SET_REPLACE"
CREATE_UPDATE = "CREATE_UPDATE"
DELETE_ONLY = "DELETE_ONLY"
REPLACE_ON_FAILURE = "REPLACE_ON_FAILURE"
16 changes: 16 additions & 0 deletions cumulus/types/codebuild/buildaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,19 @@ def __init__(self, **kwargs):
Provider="Manual"
)
self.RunOrder = "1"


class CloudFormationAction(troposphere.codepipeline.Actions):
"""
This class doesn't do much except set the ActionType to reduce code clutter
"""
def __init__(self, **kwargs):
super(CloudFormationAction, self).__init__(**kwargs)

self.ActionTypeId = troposphere.codepipeline.ActionTypeId(
Category="Deploy",
Owner="AWS",
Version="1",
Provider="CloudFormation"
)
self.RunOrder = "1"

0 comments on commit 669033e

Please sign in to comment.