diff --git a/.gitignore b/.gitignore index 1c49e77..7acdf1b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__/ # Distribution / packaging .Python +.vscode env/ build/ develop-eggs/ diff --git a/cumulus/__init__.py b/cumulus/__init__.py index 6f50cf1..3648f27 100644 --- a/cumulus/__init__.py +++ b/cumulus/__init__.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- - """Top-level package for Cumulus.""" __author__ = """Brett Swift""" __email__ = 'brettswift@gmail.com' __version__ = '0.1.5' + diff --git a/cumulus/components/__init__.py b/cumulus/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cumulus/components/userdata/__init__.py b/cumulus/components/userdata/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cumulus/components/userdata/linux.py b/cumulus/components/userdata/linux.py new file mode 100644 index 0000000..b5f99f0 --- /dev/null +++ b/cumulus/components/userdata/linux.py @@ -0,0 +1,38 @@ +from troposphere import ( + Ref, + Join, +) + + +def user_data_for_cfn_init(launch_config_name, asg_name, configsets): + """ + :return: A troposphere Join object that contains userdata for use with cfn-init + + :param configsets: The single 'key' value set in the cfn-init Metadata parameter: cloudformation.InitConfigSets + :type asg_name: String name of the ASG cloudformation resource + :type launch_config_name: String name of the launch config cloudformation resource + """ + default_userdata_asg_signal = ( + Join('', + [ + "#!/bin/bash -xe\n", + "yum update aws-cfn-bootstrap\n", + "# Install the files and packages from the metadata\n", + "/opt/aws/bin/cfn-init ", + " --stack ", Ref("AWS::StackName"), + " --resource ", launch_config_name, + " --configsets %s " % configsets, + " --region ", Ref("AWS::Region"), "\n", + # "# Get exit code of cfn init to use in cfn-signal\n", + # "export init_status=$?", "\n" + # Signal regardless of existence of an update policy. + # An error will pop up in the logs but I don't think this causes a problem. + "# Signal the ASG we are ready\n\n", + "/opt/aws/bin/cfn-signal -e 0", + # "/opt/aws/bin/cfn-signal -e $init_status", + " --resource %s" % asg_name, + " --stack ", Ref("AWS::StackName"), + " --region ", Ref("AWS::Region"), + "\n" + ])) + return default_userdata_asg_signal diff --git a/cumulus/components/userdata/windows.py b/cumulus/components/userdata/windows.py new file mode 100644 index 0000000..5e751d0 --- /dev/null +++ b/cumulus/components/userdata/windows.py @@ -0,0 +1,33 @@ +from troposphere import ( + Ref, + Join, +) + + +def user_data_for_cfn_init(launch_config_name, asg_name, configsets): + """ + :return: A troposphere Join object that contains userdata for use with cfn-init + :param configsets: The single 'key' value set in the cfn-init Metadata parameter: cloudformation.InitConfigSets + :type asg_name: String name of the ASG cloudformation resource + :type launch_config_name: String name of the launch config cloudformation resource + """ + default_userdata_asg_signal = ( + Join('', + [ + "\n", + "& ", "$env:ProgramFiles\Amazon\cfn-bootstrap\cfn-init.exe", + " --stack ", Ref("AWS::StackName"), + " --resource ", launch_config_name, + " --configsets %s " % configsets, + " --region ", Ref("AWS::Region"), "\n", + "# Signal the ASG we are ready\n", + "&", "$env:ProgramFiles\Amazon\cfn-signal", + " -e ", + " $LastExitCode", + " --resource %s" % asg_name, + " --stack ", Ref("AWS::StackName"), + " --region ", Ref("AWS::Region"), + "\n", + "" + ])) + return default_userdata_asg_signal diff --git a/cumulus/steps/dev_tools/pipeline.py b/cumulus/steps/dev_tools/pipeline.py index d73b143..4464d19 100644 --- a/cumulus/steps/dev_tools/pipeline.py +++ b/cumulus/steps/dev_tools/pipeline.py @@ -184,7 +184,6 @@ def handle(self, chain_context): pipeline_policy ] ) - generic_pipeline = codepipeline.Pipeline( "Pipeline", # Name=chain_context.instance_name, diff --git a/cumulus/steps/ec2/__init__.py b/cumulus/steps/ec2/__init__.py new file mode 100644 index 0000000..6336ac3 --- /dev/null +++ b/cumulus/steps/ec2/__init__.py @@ -0,0 +1,3 @@ + +META_SECURITY_GROUP_REF = 'security_group_ref' +META_TARGET_GROUP_NAME = 'target_group_ref' diff --git a/cumulus/steps/ec2/alb.py b/cumulus/steps/ec2/alb.py new file mode 100644 index 0000000..631d8e6 --- /dev/null +++ b/cumulus/steps/ec2/alb.py @@ -0,0 +1,127 @@ +from cumulus.chain import step +from troposphere import ( + Ref, Not, Equals, Join, ec2, + If, Output +) +from troposphere import elasticloadbalancingv2 as alb + +CLUSTER_SG_NAME = "%sSG" +ALB_SG_NAME = "%sAlbSG" +ALB_NAME = "%sLoadBalancer" +TARGET_GROUP_DEFAULT = "%sTargetGroup" + + +class Alb(step.Step): + + def __init__(self, + ): + step.Step.__init__(self) + + def handle(self, chain_context): + print(chain_context.instance_name) + self.create_conditions(chain_context.template) + self.create_security_groups(chain_context.template, chain_context.instance_name) + self.create_default_target_group(chain_context.template, chain_context.instance_name) + self.create_load_balancer_alb(chain_context.template, chain_context.instance_name) + self.add_listener(chain_context.template, chain_context.instance_name) + + def create_conditions(self, template): + template.add_condition( + "UseSSL", + Not(Equals(Ref("ALBCertName"), ""))) + template.add_condition( + "UseIAMCert", + Not(Equals(Ref("ALBCertType"), "acm"))) + + def create_security_groups(self, template, instance_name): + asg_sg = CLUSTER_SG_NAME % instance_name + alb_sg = ALB_SG_NAME % instance_name + + # ALB Security group + template.add_resource( + ec2.SecurityGroup( + alb_sg, + GroupDescription=alb_sg, + VpcId=Ref("VpcId") + )) + + template.add_output( + Output("InternalAlbSG", Value=Ref(alb_sg)) + ) + + # TODO: take a list of Cidr's + # Allow Internet to connect to ALB + template.add_resource(ec2.SecurityGroupIngress( + "LocalNetworkTo%sAlbPort443" % instance_name, + IpProtocol="tcp", FromPort="443", ToPort="443", + CidrIp="10.0.0.0/0", + GroupId=Ref(alb_sg), + )) + + def create_load_balancer_alb(self, template, instance_name): + alb_name = ALB_NAME % instance_name + alb_sg = ALB_SG_NAME % instance_name + + load_balancer = template.add_resource(alb.LoadBalancer( + alb_name, + # Name=alb_name, + Scheme="internal", + Subnets=Ref("PrivateSubnets"), + SecurityGroups=[Ref(alb_sg)] + )) + + template.add_output( + Output( + "CanonicalHostedZoneID", + Value=load_balancer.GetAtt("CanonicalHostedZoneID") + ) + ) + template.add_output( + Output("DNSName", Value=load_balancer.GetAtt("DNSName")) + ) + + def add_listener(self, template, instance_name): + # Choose proper certificate source ?-> always acm? + acm_cert = Join("", [ + "arn:aws:acm:", + Ref("AWS::Region"), + ":", + Ref("AWS::AccountId"), + ":certificate/", Ref("ALBCertName")]) + # We probably don't need this code for an IAM Cert + iam_cert = Join("", [ + "arn:aws:iam::", + Ref("AWS::AccountId"), + ":server-certificate/", + Ref("ALBCertName")]) + cert_id = If("UseIAMCert", iam_cert, acm_cert) + alb_name = ALB_NAME % instance_name + + with_ssl = alb.Listener( + "Listener", + Port="443", + Protocol="HTTPS", + LoadBalancerArn=Ref(alb_name), + DefaultActions=[alb.Action( + Type="forward", + TargetGroupArn=Ref(TARGET_GROUP_DEFAULT % instance_name) + )], + Certificates=[alb.Certificate( + CertificateArn=cert_id + )] + ) + + template.add_resource(with_ssl) + + template.add_output( + Output("IAlbListener", Value=with_ssl.Ref()) + ) + + def create_default_target_group(self, template, instance_name): + print(template.__dict__) + template.add_resource(alb.TargetGroup( + TARGET_GROUP_DEFAULT % instance_name, + Port='80', + Protocol="HTTP", + VpcId=Ref("VpcId"), + )) diff --git a/cumulus/steps/ec2/alb_port.py b/cumulus/steps/ec2/alb_port.py new file mode 100644 index 0000000..5185b25 --- /dev/null +++ b/cumulus/steps/ec2/alb_port.py @@ -0,0 +1,31 @@ +from troposphere import ( + Ref, ec2, Join) +from cumulus.chain import step +from cumulus.steps.ec2 import META_SECURITY_GROUP_REF + + +class AlbPort(step.Step): + + def __init__(self, + port_to_open, + alb_sg_name): + + step.Step.__init__(self) + + self.port_to_open = port_to_open + self.alb_sg_name = alb_sg_name + + def handle(self, chain_context): + template = chain_context.template + + name = '%sElbToASGPort%s' % (chain_context.instance_name, self.port_to_open) + + template.add_resource(ec2.SecurityGroupIngress( + name, + IpProtocol="tcp", + FromPort=self.port_to_open, + ToPort=self.port_to_open, + SourceSecurityGroupId=Ref(self.alb_sg_name), + GroupId=chain_context.metadata[META_SECURITY_GROUP_REF] + )) + diff --git a/cumulus/steps/ec2/block_device_data.py b/cumulus/steps/ec2/block_device_data.py new file mode 100644 index 0000000..876664e --- /dev/null +++ b/cumulus/steps/ec2/block_device_data.py @@ -0,0 +1,17 @@ +from cumulus.chain import step +from cumulus.util.tropo import TemplateQuery +from troposphere import autoscaling + + + +class BlockDeviceData(step.Step): + + def __init__(self, + volume): + step.Step.__init__(self) + self.volume = volume + + def handle(self, chain_context): + launchConfig = TemplateQuery.get_resource_by_type(template=chain_context.template, + type_to_find=autoscaling.LaunchConfiguration)[0] + launchConfig.properties['BlockDeviceMappings'] = [self.volume] diff --git a/cumulus/steps/ec2/dns.py b/cumulus/steps/ec2/dns.py new file mode 100644 index 0000000..e4ec1de --- /dev/null +++ b/cumulus/steps/ec2/dns.py @@ -0,0 +1,50 @@ +from troposphere import route53 +from troposphere import ( + Ref, Join, ec2) +from cumulus.chain import step + + +class Dns(step.Step): + + def __init__(self, + base_domain, + hosted_zone_id, + dns_name, + ): + + step.Step.__init__(self) + + self.base_domain = base_domain + self.hosted_zone_id = hosted_zone_id + self.dns_name = dns_name + + def handle(self, chain_context): + template = chain_context.template + + name = 'AlbAlias%s' % chain_context.instance_name + + template.add_resource(route53.RecordSetGroup( + "Route53Records", + RecordSets=[ + route53.RecordSet( + name, + Weight=1, + SetIdentifier="original", + AliasTarget=route53.AliasTarget( + HostedZoneId=self.hosted_zone_id, + DNSName=self.dns_name, + EvaluateTargetHealth=False, + ), + Name=Join("", [ + Ref("namespace"), + "-", + Ref("env"), + ".", + self.base_domain, + "." + ]), + Type="A", + ) + ], + HostedZoneName=Join("", [self.base_domain, "."]) + )) diff --git a/cumulus/steps/ec2/ingress_rule.py b/cumulus/steps/ec2/ingress_rule.py new file mode 100644 index 0000000..35992ac --- /dev/null +++ b/cumulus/steps/ec2/ingress_rule.py @@ -0,0 +1,33 @@ +from troposphere import elasticloadbalancingv2 as alb +from troposphere import ( + Ref, ec2) +import re +from cumulus.chain import step +from cumulus.steps.ec2 import META_SECURITY_GROUP_REF + + +class IngressRule(step.Step): + + def __init__(self, + port_to_open, + cidr): + + step.Step.__init__(self) + + self.port_to_open = port_to_open + self.cidr = cidr + + def handle(self, chain_context): + template = chain_context.template + + clean_cidr = re.compile('[\W_]+').sub('', self.cidr) + + template.add_resource(ec2.SecurityGroupIngress( + "Cidr%sToASGPort%s" % (clean_cidr, self.port_to_open), + IpProtocol="tcp", + FromPort=self.port_to_open, + ToPort=self.port_to_open, + CidrIp=self.cidr, + GroupId=chain_context.metadata[META_SECURITY_GROUP_REF] + )) + diff --git a/cumulus/steps/ec2/instance_profile_role.py b/cumulus/steps/ec2/instance_profile_role.py new file mode 100644 index 0000000..38a593a --- /dev/null +++ b/cumulus/steps/ec2/instance_profile_role.py @@ -0,0 +1,32 @@ +from troposphere import Ref + +from cumulus.chain import step +from troposphere.iam import InstanceProfile + +from cumulus.util.tropo import TemplateQuery + + +class InstanceProfileRole(step.Step): + + def __init__(self, + instance_profile_name, + role): + step.Step.__init__(self) + self.instance_profile_name = instance_profile_name + self.role = role + + def handle(self, chain_context): + template = chain_context.template + + template.add_resource(self.role) + + try: + instanceProfile = TemplateQuery.get_resource_by_title(template, self.instance_profile_name) + instanceProfile.properties['Roles'].append(Ref(self.role)) + except ValueError: + print('Adding new Instance Profile') + template.add_resource(InstanceProfile( + self.instance_profile_name, + Roles=[Ref(self.role)] + )) + diff --git a/cumulus/steps/ec2/launch_config.py b/cumulus/steps/ec2/launch_config.py new file mode 100644 index 0000000..c085309 --- /dev/null +++ b/cumulus/steps/ec2/launch_config.py @@ -0,0 +1,74 @@ +from troposphere import autoscaling, Ref, FindInMap, Base64, ec2 +from cumulus.chain import step +import cumulus.components.userdata.linux +from cumulus.steps.ec2 import META_SECURITY_GROUP_REF + + +class LaunchConfig(step.Step): + + def __init__(self, + meta_data, + vpc_id=None, + user_data=None, ): + + step.Step.__init__(self) + + self.user_data = user_data + self.meta_data = meta_data + + self.vpc_id = vpc_id + + def handle(self, chain_context): + + lc_name = 'Lc%s' % chain_context.instance_name + + template = chain_context.template + + template.add_resource(ec2.SecurityGroup( + "ScalingGroupSecurityGroup", + GroupDescription="ScalingGroupSecurityGroup description", + **self._get_security_group_parameters())) + + chain_context.metadata[META_SECURITY_GROUP_REF] = Ref("ScalingGroupSecurityGroup") + + if not self.user_data: + user_data = cumulus.components.userdata.linux.user_data_for_cfn_init( + launch_config_name=lc_name, + asg_name="Asg%s" % chain_context.instance_name, + configsets='default', # TODO: Fix this + ) + + launch_config = autoscaling.LaunchConfiguration( + lc_name, + UserData=Base64(user_data), + Metadata=self.meta_data, + IamInstanceProfile=Ref(chain_context.instance_name), + **self._get_launch_configuration_parameters(chain_context) + ) + + template.add_resource(launch_config) + + def _get_security_group_parameters(self): + config = { + } + + if self.vpc_id: + config['VpcId'] = self.vpc_id + + return config + + def _get_launch_configuration_parameters(self, chain_context): + + asg_sg_list = [chain_context.metadata[META_SECURITY_GROUP_REF]] + + parameters = { + 'ImageId': FindInMap('AmiMap', + Ref("AWS::Region"), + Ref('ImageName')), + 'InstanceType': Ref("InstanceType"), + 'KeyName': Ref("SshKeyName"), + 'SecurityGroups': asg_sg_list, + } + + return parameters + diff --git a/cumulus/steps/ec2/listener_rule.py b/cumulus/steps/ec2/listener_rule.py new file mode 100644 index 0000000..ef29892 --- /dev/null +++ b/cumulus/steps/ec2/listener_rule.py @@ -0,0 +1,63 @@ +from troposphere import elasticloadbalancingv2 as alb, Ref +from cumulus.chain import step +from cumulus.steps.ec2 import META_TARGET_GROUP_NAME + + +class ListenerRule(step.Step): + + def __init__(self, + base_domain_name, + alb_listener_rule, + priority, + path_pattern=None, + host_pattern=None, + ): + + step.Step.__init__(self) + + self.path_pattern = path_pattern + self.host_pattern = host_pattern + self.priority = priority + self.base_domain_name = base_domain_name + self.alb_listener_rule = alb_listener_rule + + def handle(self, chain_context): + + template = chain_context.template + + if not (self.path_pattern or self.host_pattern): + raise RuntimeError("with_listener_rule() requires one of: path_pattern, host_pattern") + + routing_condition = None + if self.path_pattern: + routing_condition = alb.Condition( + Field="path-pattern", + Values=[self.path_pattern], + ) + # TODO: support host headers someday + # elif self.host_pattern: + # routing_condition = alb.Condition( + # Field="host-header", + # Values=[ + # chain_context.instance_name, + # "-", + # What do we put here? + # ".", + # self.base_domain_name + # ] + # ) + + name = "%sAlbListenerRule" % chain_context.instance_name + + listener_rule = alb.ListenerRule( + name, + ListenerArn=self.alb_listener_rule, + Conditions=[routing_condition], + Actions=[alb.Action( + Type="forward", + TargetGroupArn=Ref(chain_context.metadata[META_TARGET_GROUP_NAME]) + )], + Priority=self.priority, + ) + + template.add_resource(listener_rule) diff --git a/cumulus/steps/ec2/scaling_group.py b/cumulus/steps/ec2/scaling_group.py new file mode 100644 index 0000000..357f6e3 --- /dev/null +++ b/cumulus/steps/ec2/scaling_group.py @@ -0,0 +1,59 @@ +from troposphere import Ref, autoscaling, FindInMap, Base64, ec2 +from troposphere.autoscaling import Tag as ASTag +from troposphere.policies import UpdatePolicy, AutoScalingReplacingUpdate, AutoScalingRollingUpdate +from cumulus.chain import step +from cumulus.steps.ec2 import META_TARGET_GROUP_NAME + + +class ScalingGroup(step.Step): + + def __init__(self, + use_update_policy=True + ): + """ + :type launch_type: LaunchType: the type of the ec2 that will be created + """ + step.Step.__init__(self) + + # Set default resource names for those not injected + self.use_update_policy = use_update_policy + + def handle(self, chain_context): + + template = chain_context.template + + name = "Asg%s" % chain_context.instance_name + + template.add_resource(autoscaling.AutoScalingGroup( + name, + **self._get_autoscaling_group_parameters(chain_context=chain_context, + launch_config_name='Lc%s' % chain_context.instance_name))) + + def _get_autoscaling_group_parameters(self, chain_context, launch_config_name): + config = { + 'AvailabilityZones': Ref("AvailabilityZones"), # Not really required in this case (yet) + 'LaunchConfigurationName': Ref(launch_config_name), + 'MinSize': Ref("MinSize"), + 'MaxSize': Ref("MaxSize"), + 'VPCZoneIdentifier': Ref("PrivateSubnets"), + 'Tags': [ASTag('Name', chain_context.instance_name, True)], + } + + if chain_context.metadata[META_TARGET_GROUP_NAME]: + config['TargetGroupARNs'] = [Ref(chain_context.metadata[META_TARGET_GROUP_NAME])] + + if self.use_update_policy: + update_policy = UpdatePolicy( + AutoScalingReplacingUpdate=AutoScalingReplacingUpdate( + WillReplace=True, + ), + AutoScalingRollingUpdate=AutoScalingRollingUpdate( + PauseTime='PT5M', + MinInstancesInService="1", + MaxBatchSize='1', + WaitOnResourceSignals=True + ) + ) + config['UpdatePolicy'] = update_policy + + return config diff --git a/cumulus/steps/ec2/target_group.py b/cumulus/steps/ec2/target_group.py new file mode 100644 index 0000000..ab5df34 --- /dev/null +++ b/cumulus/steps/ec2/target_group.py @@ -0,0 +1,42 @@ +from troposphere import elasticloadbalancingv2 as alb, route53 +from troposphere import ( + Ref, Join, ec2) +from cumulus.chain import step +from cumulus.steps.ec2 import META_TARGET_GROUP_NAME + + +class TargetGroup(step.Step): + + def __init__(self, + port, + vpc_id + ): + + step.Step.__init__(self) + + self.port = port + self.vpc_id = vpc_id + + def handle(self, chain_context): + + # todo: why is this not allowing a reference? + + name = '%sTargetGroup' % chain_context.instance_name + + chain_context.metadata[META_TARGET_GROUP_NAME] = name + template = chain_context.template + + template.add_resource(alb.TargetGroup( + name, + HealthCheckPath="/", + HealthCheckIntervalSeconds="30", + HealthCheckProtocol="HTTP", + HealthCheckTimeoutSeconds="10", + HealthyThresholdCount="4", + Matcher=alb.Matcher(HttpCode="200"), + Port=self.port, + Protocol="HTTP", + UnhealthyThresholdCount="3", + VpcId=self.vpc_id + )) + diff --git a/cumulus/types.py b/cumulus/types.py new file mode 100644 index 0000000..0983357 --- /dev/null +++ b/cumulus/types.py @@ -0,0 +1,3 @@ +class LaunchType: + """ enum used by launch configuration or anything OS specific""" + LINUX, WINDOWS = range(2) diff --git a/tests/stacker_test/blueprints/alb.py b/tests/stacker_test/blueprints/alb.py new file mode 100644 index 0000000..a64bba5 --- /dev/null +++ b/tests/stacker_test/blueprints/alb.py @@ -0,0 +1,67 @@ +from awacs.aws import Allow, Principal, Policy, Statement +from awacs.sts import AssumeRole +from stacker.blueprints.base import Blueprint +from stacker.blueprints.variables.types import EC2VPCId, EC2SubnetIdList, CFNCommaDelimitedList, CFNString, CFNNumber, \ + EC2KeyPairKeyName +from troposphere import cloudformation, ec2, iam, Ref +from troposphere.iam import Role +from cumulus.chain import chain, chaincontext +from cumulus.steps.ec2 import scaling_group, launch_config, block_device_data, ingress_rule, target_group, dns, alb +from cumulus.steps.ec2.instance_profile_role import InstanceProfileRole + + +class Alb(Blueprint): + VARIABLES = { + 'VpcId': {'type': EC2VPCId, 'description': 'Vpc Id'}, + 'PrivateSubnets': { + 'type': EC2SubnetIdList, + 'description': 'Subnets to deploy private ' + 'instances in.'}, + 'AvailabilityZones': {'type': CFNCommaDelimitedList, + 'description': 'Availability Zones to deploy ' + 'instances in.'}, + 'InstanceType': {'type': CFNString, + 'description': 'EC2 Instance Type', + 'default': 't2.micro'}, + 'MinSize': {'type': CFNNumber, + 'description': 'Minimum # of instances.', + 'default': '1'}, + 'MaxSize': {'type': CFNNumber, + 'description': 'Maximum # of instances.', + 'default': '5'}, + 'ALBHostName': { + 'type': CFNString, + 'description': 'A hostname to give to the ALB. If not given ' + 'no ALB will be created.', + 'default': ''}, + 'ALBCertName': { + 'type': CFNString, + 'description': 'The SSL certificate name to use on the ALB.', + 'default': ''}, + 'ALBCertType': { + 'type': CFNString, + 'description': 'The SSL certificate type to use on the ALB.', + 'default': 'acm'}, + } + + def create_template(self): + + instance_profile_name = "InstanceProfile" + self.name + + t = self.template + t.add_description("Acceptance Tests for cumulus scaling groups") + + instance = self.context.environment['namespace'] + self.context.environment['env'] + + the_chain = chain.Chain() + + + the_chain.add(alb.Alb( + )) + + chain_context = chaincontext.ChainContext( + template=t, + instance_name=instance + ) + + the_chain.run(chain_context) diff --git a/tests/stacker_test/blueprints/scaling_group_simple.py b/tests/stacker_test/blueprints/scaling_group_simple.py new file mode 100644 index 0000000..40dd044 --- /dev/null +++ b/tests/stacker_test/blueprints/scaling_group_simple.py @@ -0,0 +1,110 @@ +from awacs.aws import Allow, Principal, Policy, Statement +from awacs.sts import AssumeRole +from stacker.blueprints.base import Blueprint +from stacker.blueprints.variables.types import EC2VPCId, EC2SubnetIdList, CFNCommaDelimitedList, CFNString, CFNNumber, \ + EC2KeyPairKeyName +from troposphere import cloudformation, ec2, iam +from troposphere.iam import Role +from cumulus.chain import chain, chaincontext +from cumulus.steps.ec2 import scaling_group, launch_config, security_group, block_device_data, ingress_rule +from cumulus.steps.ec2.instance_profile_role import InstanceProfileRole + + +class ScalingGroupSimple(Blueprint): + VARIABLES = { + 'VpcId': {'type': EC2VPCId, + 'description': 'Vpc Id'}, + 'PrivateSubnets': {'type': EC2SubnetIdList, + 'description': 'Subnets to deploy private ' + 'instances in.'}, + 'AvailabilityZones': {'type': CFNCommaDelimitedList, + 'description': 'Availability Zones to deploy ' + 'instances in.'}, + 'InstanceType': {'type': CFNString, + 'description': 'EC2 Instance Type', + 'default': 't2.micro'}, + 'MinSize': {'type': CFNNumber, + 'description': 'Minimum # of instances.', + 'default': '1'}, + 'MaxSize': {'type': CFNNumber, + 'description': 'Maximum # of instances.', + 'default': '5'}, + 'SshKeyName': {'type': EC2KeyPairKeyName}, + 'ImageName': { + 'type': CFNString, + 'description': 'The image name to use from the AMIMap (usually ' + 'found in the config file.)'}, + } + + def get_metadata(self): + metadata = cloudformation.Metadata( + cloudformation.Init( + cloudformation.InitConfigSets( + default=['install_and_run'] + ), + install_and_run=cloudformation.InitConfig( + commands={ + '01-startup': { + 'command': 'echo hello world' + }, + } + ) + ) + ) + return metadata + + def create_template(self): + t = self.template + t.add_description("Acceptance Tests for cumulus scaling groups") + + # TODO fix + # instance = self.name + self.context.environment['env'] + instance = "someinstance" + # TODO: give to builder + the_chain = chain.Chain() + + the_chain.add(ingress_rule.IngressRule( + port_to_open="22", + cidr="10.0.0.0/8" + )) + + instance_profile_name = "InstanceProfile" + self.name + + the_chain.add(InstanceProfileRole( + instance_profile_name=instance_profile_name, + role=iam.Role( + "SomeRoleName1", + AssumeRolePolicyDocument=Policy( + Statement=[ + Statement( + Effect=Allow, + Action=[AssumeRole], + Principal=Principal("Service", ["ec2.amazonaws.com", "s3.amazonaws.com"]) + ) + ] + ), + ))) + + launchConfigName = 'lc' + self.name + + the_chain.add(launch_config.LaunchConfig(asg_name=self.name, + launch_config_name=launchConfigName, + meta_data=self.get_metadata(), + instance_profile_name=instance_profile_name), ) + + the_chain.add(block_device_data.BlockDeviceData(ec2.BlockDeviceMapping( + DeviceName="/dev/xvda", + Ebs=ec2.EBSBlockDevice( + VolumeSize="40" + )))) + + the_chain.add(scaling_group.ScalingGroup( + launch_config_name=launchConfigName, + )) + + chain_context = chaincontext.ChainContext( + template=t, + instance_name=instance + ) + + the_chain.run(chain_context) diff --git a/tests/stacker_test/blueprints/website_simple.py b/tests/stacker_test/blueprints/website_simple.py new file mode 100644 index 0000000..61deadd --- /dev/null +++ b/tests/stacker_test/blueprints/website_simple.py @@ -0,0 +1,161 @@ +from awacs.aws import Allow, Principal, Policy, Statement +from awacs.sts import AssumeRole +from stacker.blueprints.base import Blueprint +from stacker.blueprints.variables.types import EC2VPCId, EC2SubnetIdList, CFNCommaDelimitedList, CFNString, CFNNumber, \ + EC2KeyPairKeyName +from troposphere import cloudformation, ec2, iam, Ref +from troposphere.iam import Role +from cumulus.chain import chain, chaincontext +from cumulus.steps.ec2 import scaling_group, launch_config, block_device_data, ingress_rule, target_group, dns, \ + alb_port, listener_rule +from cumulus.steps.ec2.instance_profile_role import InstanceProfileRole + + +class WebsiteSimple(Blueprint): + VARIABLES = { + 'namespace': { + 'type': CFNString + }, + 'env': { + 'type': CFNString + }, + 'IAlbListener': { + 'type': CFNString, + 'description': 'From the ALB', + }, + 'AlbCanonicalHostedZoneID': { + 'type': CFNString, + 'description': 'From the ALB', + 'default': 'acm', + }, + 'AlbDNSName': { + 'type': CFNString, + 'description': 'From the ALB', + 'default': 'acm', + }, + 'AlbSg': { + 'type': CFNString, + 'description': 'From the ALB', + }, + 'InstanceType': {'type': CFNString, + 'description': 'EC2 Instance Type', + 'default': 't2.micro'}, + 'SshKeyName': {'type': EC2KeyPairKeyName}, + 'ImageName': { + 'type': CFNString, + 'description': 'The image name to use from the AMIMap (usually ' + 'found in the config file.)'}, + 'BaseDomain': { + 'type': CFNString}, + 'MinSize': {'type': CFNNumber, + 'description': 'Minimum # of instances.', + 'default': '1'}, + 'MaxSize': {'type': CFNNumber, + 'description': 'Maximum # of instances.', + 'default': '5'}, + 'PrivateSubnets': {'type': EC2SubnetIdList, + 'description': 'Subnets to deploy private ' + 'instances in.'}, + 'AvailabilityZones': {'type': CFNCommaDelimitedList, + 'description': 'Availability Zones to deploy ' + 'instances in.'}, + 'VpcId': {'type': CFNString, + 'description': 'Vpc Id'}, + } + + def get_metadata(self): + metadata = cloudformation.Metadata( + cloudformation.Init( + cloudformation.InitConfigSets( + default=['install_and_run'] + ), + install_and_run=cloudformation.InitConfig( + commands={ + '01-startup': { + 'command': 'nohup python -m SimpleHTTPServer 8000 &' + }, + } + ) + ) + ) + return metadata + + def create_template(self): + t = self.template + t.add_description("Acceptance Tests for cumulus scaling groups") + + # TODO fix + # instance = self.name + self.context.environment['env'] + # TODO: give to builder + the_chain = chain.Chain() + + application_port = "8000" + + instance_profile_name = "InstanceProfile" + self.name + + the_chain.add(InstanceProfileRole( + instance_profile_name=instance_profile_name, + role=iam.Role( + "SomeRoleName1", + AssumeRolePolicyDocument=Policy( + Statement=[ + Statement( + Effect=Allow, + Action=[AssumeRole], + Principal=Principal("Service", ["ec2.amazonaws.com", "s3.amazonaws.com"]) + ) + ] + ), + ))) + + the_chain.add(launch_config.LaunchConfig(meta_data=self.get_metadata(), + vpc_id=Ref("VpcId"))) + + the_chain.add(block_device_data.BlockDeviceData(ec2.BlockDeviceMapping( + DeviceName="/dev/xvda", + Ebs=ec2.EBSBlockDevice( + VolumeSize="40" + )))) + + the_chain.add(target_group.TargetGroup( + port=application_port, + vpc_id=Ref("VpcId") + )) + + the_chain.add(scaling_group.ScalingGroup( + )) + + the_chain.add(ingress_rule.IngressRule( + port_to_open="22", + cidr="10.0.0.0/8" + )) + + the_chain.add(ingress_rule.IngressRule( + port_to_open=application_port, + cidr="10.0.0.0/8" + )) + + the_chain.add(dns.Dns( + base_domain=Ref("BaseDomain"), + hosted_zone_id=Ref("AlbCanonicalHostedZoneID"), + dns_name=Ref("AlbDNSName"), + )) + + the_chain.add(alb_port.AlbPort( + port_to_open=application_port, + alb_sg_name="AlbSg", + )) + + the_chain.add(listener_rule.ListenerRule( + base_domain_name=Ref("BaseDomain"), + alb_listener_rule=Ref("IAlbListener"), + path_pattern="/*", + priority="2" + )) + + chain_context = chaincontext.ChainContext( + template=t, + instance_name=instance_profile_name + ) + + the_chain.run(chain_context) diff --git a/tests/stacker_test/conf/asgtest.env b/tests/stacker_test/conf/asgtest.env new file mode 100644 index 0000000..bf74ed1 --- /dev/null +++ b/tests/stacker_test/conf/asgtest.env @@ -0,0 +1,10 @@ +namespace: jonsapp +env: dev +BaseDomain: playpen.dsl.aws.shaw.ca +VpcId: vpc-894b89ef +BaseDomain: playpen.dsl.aws.shaw.ca +PrivateSubnets: subnet-7b8cba32,subnet-ed041b8a +SshKeyName: stc-admin-March-2017-PLAYPEN +ALBCertName: ${ssmstore us-west-2@/simpleweb/bswift/ALBCertName} +# ALBCertName: 9967b9b9-f046-419a-a496-859bf938acd8 + diff --git a/tests/stacker_test/somefile.yaml b/tests/stacker_test/somefile.yaml new file mode 100644 index 0000000..bfdecd7 --- /dev/null +++ b/tests/stacker_test/somefile.yaml @@ -0,0 +1,1120 @@ +Description: Acceptance Tests for cumulus scaling groups +Mappings: + AmiMap: + us-west-2: + amazonLinux2: ami-a9d09ed1 +Resources: + InstanceProfilewebsitesimple: + Properties: + Roles: + - !Ref 'SomeRoleName1' + Type: AWS::IAM::InstanceProfile + Route53Records: + Properties: + HostedZoneName: !Join + - '' + - - !Ref 'BaseDomain' + - . + RecordSets: + - AliasTarget: + DNSName: !Ref 'ALBDNSName' + EvaluateTargetHealth: 'false' + HostedZoneId: !Ref 'AlbCanonicalHostedZoneID' + Name: !Join + - '' + - - cumulus + - '-' + - cumulusdns + - . + - !Ref 'BaseDomain' + - . + SetIdentifier: original + Type: A + Weight: 1 + Type: AWS::Route53::RecordSetGroup + SgCidrAccessCidr100008ToASGPort22: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '22' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '22' + Type: AWS::EC2::SecurityGroupIngress + SgCidrAccessCidr100008ToASGPort8000: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '8000' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '8000' + Type: AWS::EC2::SecurityGroupIngress + SomeRoleName1: + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + - s3.amazonaws.com + Type: AWS::IAM::Role + asgwebsitesimple: + Properties: + AvailabilityZones: !Ref 'AvailabilityZones' + LaunchConfigurationName: !Ref 'lcwebsitesimple' + MaxSize: !Ref 'MaxSize' + MinSize: !Ref 'MinSize' + Tags: + - Key: Name + PropagateAtLaunch: true + Value: cumulustestscalinggroupInstanceProfilewebsitesimple + - Key: CE-OffHours + PropagateAtLaunch: true + Value: 'off' + TargetGroupARNs: + - !Ref 'cumulustesttargetgroup' + VPCZoneIdentifier: !Ref 'PrivateSubnets' + Type: AWS::AutoScaling::AutoScalingGroup + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: 'true' + AutoScalingRollingUpdate: + MaxBatchSize: '1' + MinInstancesInService: '1' + PauseTime: PT5M + WaitOnResourceSignals: 'true' + cumulustesttargetgroup: + Properties: + HealthCheckIntervalSeconds: '30' + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: '10' + HealthyThresholdCount: '4' + Matcher: + HttpCode: '200' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: '3' + VpcId: !Ref 'VpcId' + Type: AWS::ElasticLoadBalancingV2::TargetGroup + lcwebsitesimple: + Metadata: + AWS::CloudFormation::Init: + configSets: + default: + - install_and_run + install_and_run: + commands: + '01-startup': + command: nohup python -m SimpleHTTPServer 8000 & + Properties: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: '40' + IamInstanceProfile: !Ref 'InstanceProfilewebsitesimple' + ImageId: !FindInMap + - AmiMap + - !Ref 'AWS::Region' + - !Ref 'ImageName' + InstanceType: !Ref 'InstanceType' + KeyName: !Ref 'SshKeyName' + SecurityGroups: + - !Ref 'sg1websitesimple' + - !Ref 'sg2websitesimple' + UserData: !Base64 + Fn::Join: + - '' + - - "#!/bin/bash -xe\n" + - "yum update aws-cfn-bootstrap\n" + - "# Install the files and packages from the metadata\n" + - '/opt/aws/bin/cfn-init ' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --resource ' + - lcwebsitesimple + - ' --configsets default ' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + - "# Signal the ASG we are ready\n\n" + - /opt/aws/bin/cfn-signal -e 0 + - ' --resource websitesimple' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + Type: AWS::AutoScaling::LaunchConfiguration + sg1websitesimple: + Properties: + GroupDescription: sg1websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup + sg2websitesimple: + Properties: + GroupDescription: sg2websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup +Description: Acceptance Tests for cumulus scaling groups +Mappings: + AmiMap: + us-west-2: + amazonLinux2: ami-a9d09ed1 +Resources: + InstanceProfilewebsitesimple: + Properties: + Roles: + - !Ref 'SomeRoleName1' + Type: AWS::IAM::InstanceProfile + Route53Records: + Properties: + HostedZoneName: !Join + - '' + - - !Ref 'BaseDomain' + - . + RecordSets: + - AliasTarget: + DNSName: !Ref 'ALBDNSName' + EvaluateTargetHealth: 'false' + HostedZoneId: !Ref 'AlbCanonicalHostedZoneID' + Name: !Join + - '' + - - cumulus + - '-' + - cumulusdns + - . + - !Ref 'BaseDomain' + - . + SetIdentifier: original + Type: A + Weight: 1 + Type: AWS::Route53::RecordSetGroup + SgCidrAccessCidr100008ToASGPort22: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '22' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '22' + Type: AWS::EC2::SecurityGroupIngress + SgCidrAccessCidr100008ToASGPort8000: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '8000' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '8000' + Type: AWS::EC2::SecurityGroupIngress + SomeRoleName1: + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + - s3.amazonaws.com + Type: AWS::IAM::Role + asgwebsitesimple: + Properties: + AvailabilityZones: !Ref 'AvailabilityZones' + LaunchConfigurationName: !Ref 'lcwebsitesimple' + MaxSize: !Ref 'MaxSize' + MinSize: !Ref 'MinSize' + Tags: + - Key: Name + PropagateAtLaunch: true + Value: cumulustestscalinggroupInstanceProfilewebsitesimple + - Key: CE-OffHours + PropagateAtLaunch: true + Value: 'off' + TargetGroupARNs: + - !Ref 'cumulustesttargetgroup' + VPCZoneIdentifier: !Ref 'PrivateSubnets' + Type: AWS::AutoScaling::AutoScalingGroup + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: 'true' + AutoScalingRollingUpdate: + MaxBatchSize: '1' + MinInstancesInService: '1' + PauseTime: PT5M + WaitOnResourceSignals: 'true' + cumulustesttargetgroup: + Properties: + HealthCheckIntervalSeconds: '30' + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: '10' + HealthyThresholdCount: '4' + Matcher: + HttpCode: '200' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: '3' + VpcId: !Ref 'VpcId' + Type: AWS::ElasticLoadBalancingV2::TargetGroup + lcwebsitesimple: + Metadata: + AWS::CloudFormation::Init: + configSets: + default: + - install_and_run + install_and_run: + commands: + '01-startup': + command: nohup python -m SimpleHTTPServer 8000 & + Properties: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: '40' + IamInstanceProfile: !Ref 'InstanceProfilewebsitesimple' + ImageId: !FindInMap + - AmiMap + - !Ref 'AWS::Region' + - !Ref 'ImageName' + InstanceType: !Ref 'InstanceType' + KeyName: !Ref 'SshKeyName' + SecurityGroups: + - !Ref 'sg1websitesimple' + - !Ref 'sg2websitesimple' + UserData: !Base64 + Fn::Join: + - '' + - - "#!/bin/bash -xe\n" + - "yum update aws-cfn-bootstrap\n" + - "# Install the files and packages from the metadata\n" + - '/opt/aws/bin/cfn-init ' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --resource ' + - lcwebsitesimple + - ' --configsets default ' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + - "# Signal the ASG we are ready\n\n" + - /opt/aws/bin/cfn-signal -e 0 + - ' --resource websitesimple' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + Type: AWS::AutoScaling::LaunchConfiguration + sg1websitesimple: + Properties: + GroupDescription: sg1websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup + sg2websitesimple: + Properties: + GroupDescription: sg2websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup +Description: Acceptance Tests for cumulus scaling groups +Mappings: + AmiMap: + us-west-2: + amazonLinux2: ami-a9d09ed1 +Resources: + InstanceProfilewebsitesimple: + Properties: + Roles: + - !Ref 'SomeRoleName1' + Type: AWS::IAM::InstanceProfile + Route53Records: + Properties: + HostedZoneName: !Join + - '' + - - !Ref 'BaseDomain' + - . + RecordSets: + - AliasTarget: + DNSName: !Ref 'ALBDNSName' + EvaluateTargetHealth: 'false' + HostedZoneId: !Ref 'AlbCanonicalHostedZoneID' + Name: !Join + - '' + - - cumulus + - '-' + - cumulusdns + - . + - !Ref 'BaseDomain' + - . + SetIdentifier: original + Type: A + Weight: 1 + Type: AWS::Route53::RecordSetGroup + SgCidrAccessCidr100008ToASGPort22: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '22' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '22' + Type: AWS::EC2::SecurityGroupIngress + SgCidrAccessCidr100008ToASGPort8000: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '8000' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '8000' + Type: AWS::EC2::SecurityGroupIngress + SomeRoleName1: + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + - s3.amazonaws.com + Type: AWS::IAM::Role + asgwebsitesimple: + Properties: + AvailabilityZones: !Ref 'AvailabilityZones' + LaunchConfigurationName: !Ref 'lcwebsitesimple' + MaxSize: !Ref 'MaxSize' + MinSize: !Ref 'MinSize' + Tags: + - Key: Name + PropagateAtLaunch: true + Value: cumulustestscalinggroupInstanceProfilewebsitesimple + - Key: CE-OffHours + PropagateAtLaunch: true + Value: 'off' + TargetGroupARNs: + - !Ref 'cumulustesttargetgroup' + VPCZoneIdentifier: !Ref 'PrivateSubnets' + Type: AWS::AutoScaling::AutoScalingGroup + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: 'true' + AutoScalingRollingUpdate: + MaxBatchSize: '1' + MinInstancesInService: '1' + PauseTime: PT5M + WaitOnResourceSignals: 'true' + cumulustesttargetgroup: + Properties: + HealthCheckIntervalSeconds: '30' + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: '10' + HealthyThresholdCount: '4' + Matcher: + HttpCode: '200' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: '3' + VpcId: !Ref 'VpcId' + Type: AWS::ElasticLoadBalancingV2::TargetGroup + lcwebsitesimple: + Metadata: + AWS::CloudFormation::Init: + configSets: + default: + - install_and_run + install_and_run: + commands: + '01-startup': + command: nohup python -m SimpleHTTPServer 8000 & + Properties: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: '40' + IamInstanceProfile: !Ref 'InstanceProfilewebsitesimple' + ImageId: !FindInMap + - AmiMap + - !Ref 'AWS::Region' + - !Ref 'ImageName' + InstanceType: !Ref 'InstanceType' + KeyName: !Ref 'SshKeyName' + SecurityGroups: + - !Ref 'sg1websitesimple' + - !Ref 'sg2websitesimple' + UserData: !Base64 + Fn::Join: + - '' + - - "#!/bin/bash -xe\n" + - "yum update aws-cfn-bootstrap\n" + - "# Install the files and packages from the metadata\n" + - '/opt/aws/bin/cfn-init ' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --resource ' + - lcwebsitesimple + - ' --configsets default ' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + - "# Signal the ASG we are ready\n\n" + - /opt/aws/bin/cfn-signal -e 0 + - ' --resource websitesimple' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + Type: AWS::AutoScaling::LaunchConfiguration + sg1websitesimple: + Properties: + GroupDescription: sg1websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup + sg2websitesimple: + Properties: + GroupDescription: sg2websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup +Description: Acceptance Tests for cumulus scaling groups +Mappings: + AmiMap: + us-west-2: + amazonLinux2: ami-a9d09ed1 +Resources: + InstanceProfilewebsitesimple: + Properties: + Roles: + - !Ref 'SomeRoleName1' + Type: AWS::IAM::InstanceProfile + Route53Records: + Properties: + HostedZoneName: !Join + - '' + - - !Ref 'BaseDomain' + - . + RecordSets: + - AliasTarget: + DNSName: !Ref 'ALBDNSName' + EvaluateTargetHealth: 'false' + HostedZoneId: !Ref 'AlbCanonicalHostedZoneID' + Name: !Join + - '' + - - cumulus + - '-' + - cumulusdns + - . + - !Ref 'BaseDomain' + - . + SetIdentifier: original + Type: A + Weight: 1 + Type: AWS::Route53::RecordSetGroup + SgCidrAccessCidr100008ToASGPort22: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '22' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '22' + Type: AWS::EC2::SecurityGroupIngress + SgCidrAccessCidr100008ToASGPort8000: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '8000' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '8000' + Type: AWS::EC2::SecurityGroupIngress + SomeRoleName1: + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + - s3.amazonaws.com + Type: AWS::IAM::Role + asgwebsitesimple: + Properties: + AvailabilityZones: !Ref 'AvailabilityZones' + LaunchConfigurationName: !Ref 'lcwebsitesimple' + MaxSize: !Ref 'MaxSize' + MinSize: !Ref 'MinSize' + Tags: + - Key: Name + PropagateAtLaunch: true + Value: cumulustestscalinggroupInstanceProfilewebsitesimple + - Key: CE-OffHours + PropagateAtLaunch: true + Value: 'off' + TargetGroupARNs: + - !Ref 'cumulustesttargetgroup' + VPCZoneIdentifier: !Ref 'PrivateSubnets' + Type: AWS::AutoScaling::AutoScalingGroup + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: 'true' + AutoScalingRollingUpdate: + MaxBatchSize: '1' + MinInstancesInService: '1' + PauseTime: PT5M + WaitOnResourceSignals: 'true' + cumulustesttargetgroup: + Properties: + HealthCheckIntervalSeconds: '30' + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: '10' + HealthyThresholdCount: '4' + Matcher: + HttpCode: '200' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: '3' + VpcId: !Ref 'VpcId' + Type: AWS::ElasticLoadBalancingV2::TargetGroup + lcwebsitesimple: + Metadata: + AWS::CloudFormation::Init: + configSets: + default: + - install_and_run + install_and_run: + commands: + '01-startup': + command: nohup python -m SimpleHTTPServer 8000 & + Properties: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: '40' + IamInstanceProfile: !Ref 'InstanceProfilewebsitesimple' + ImageId: !FindInMap + - AmiMap + - !Ref 'AWS::Region' + - !Ref 'ImageName' + InstanceType: !Ref 'InstanceType' + KeyName: !Ref 'SshKeyName' + SecurityGroups: + - !Ref 'sg1websitesimple' + - !Ref 'sg2websitesimple' + UserData: !Base64 + Fn::Join: + - '' + - - "#!/bin/bash -xe\n" + - "yum update aws-cfn-bootstrap\n" + - "# Install the files and packages from the metadata\n" + - '/opt/aws/bin/cfn-init ' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --resource ' + - lcwebsitesimple + - ' --configsets default ' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + - "# Signal the ASG we are ready\n\n" + - /opt/aws/bin/cfn-signal -e 0 + - ' --resource websitesimple' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + Type: AWS::AutoScaling::LaunchConfiguration + sg1websitesimple: + Properties: + GroupDescription: sg1websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup + sg2websitesimple: + Properties: + GroupDescription: sg2websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup +Description: Acceptance Tests for cumulus scaling groups +Mappings: + AmiMap: + us-west-2: + amazonLinux2: ami-a9d09ed1 +Resources: + InstanceProfilewebsitesimple: + Properties: + Roles: + - !Ref 'SomeRoleName1' + Type: AWS::IAM::InstanceProfile + Route53Records: + Properties: + HostedZoneName: !Join + - '' + - - !Ref 'BaseDomain' + - . + RecordSets: + - AliasTarget: + DNSName: !Ref 'ALBDNSName' + EvaluateTargetHealth: 'false' + HostedZoneId: !Ref 'AlbCanonicalHostedZoneID' + Name: !Join + - '' + - - cumulus + - '-' + - cumulusdns + - . + - !Ref 'BaseDomain' + - . + SetIdentifier: original + Type: A + Weight: 1 + Type: AWS::Route53::RecordSetGroup + SgCidrAccessCidr100008ToASGPort22: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '22' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '22' + Type: AWS::EC2::SecurityGroupIngress + SgCidrAccessCidr100008ToASGPort8000: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '8000' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '8000' + Type: AWS::EC2::SecurityGroupIngress + SomeRoleName1: + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + - s3.amazonaws.com + Type: AWS::IAM::Role + asgwebsitesimple: + Properties: + AvailabilityZones: !Ref 'AvailabilityZones' + LaunchConfigurationName: !Ref 'lcwebsitesimple' + MaxSize: !Ref 'MaxSize' + MinSize: !Ref 'MinSize' + Tags: + - Key: Name + PropagateAtLaunch: true + Value: cumulustestscalinggroupInstanceProfilewebsitesimple + - Key: CE-OffHours + PropagateAtLaunch: true + Value: 'off' + TargetGroupARNs: + - !Ref 'cumulustesttargetgroup' + VPCZoneIdentifier: !Ref 'PrivateSubnets' + Type: AWS::AutoScaling::AutoScalingGroup + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: 'true' + AutoScalingRollingUpdate: + MaxBatchSize: '1' + MinInstancesInService: '1' + PauseTime: PT5M + WaitOnResourceSignals: 'true' + cumulustesttargetgroup: + Properties: + HealthCheckIntervalSeconds: '30' + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: '10' + HealthyThresholdCount: '4' + Matcher: + HttpCode: '200' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: '3' + VpcId: !Ref 'VpcId' + Type: AWS::ElasticLoadBalancingV2::TargetGroup + lcwebsitesimple: + Metadata: + AWS::CloudFormation::Init: + configSets: + default: + - install_and_run + install_and_run: + commands: + '01-startup': + command: nohup python -m SimpleHTTPServer 8000 & + Properties: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: '40' + IamInstanceProfile: !Ref 'InstanceProfilewebsitesimple' + ImageId: !FindInMap + - AmiMap + - !Ref 'AWS::Region' + - !Ref 'ImageName' + InstanceType: !Ref 'InstanceType' + KeyName: !Ref 'SshKeyName' + SecurityGroups: + - !Ref 'sg1websitesimple' + - !Ref 'sg2websitesimple' + UserData: !Base64 + Fn::Join: + - '' + - - "#!/bin/bash -xe\n" + - "yum update aws-cfn-bootstrap\n" + - "# Install the files and packages from the metadata\n" + - '/opt/aws/bin/cfn-init ' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --resource ' + - lcwebsitesimple + - ' --configsets default ' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + - "# Signal the ASG we are ready\n\n" + - /opt/aws/bin/cfn-signal -e 0 + - ' --resource websitesimple' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + Type: AWS::AutoScaling::LaunchConfiguration + sg1websitesimple: + Properties: + GroupDescription: sg1websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup + sg2websitesimple: + Properties: + GroupDescription: sg2websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup +Description: Acceptance Tests for cumulus scaling groups +Mappings: + AmiMap: + us-west-2: + amazonLinux2: ami-a9d09ed1 +Resources: + InstanceProfilewebsitesimple: + Properties: + Roles: + - !Ref 'SomeRoleName1' + Type: AWS::IAM::InstanceProfile + Route53Records: + Properties: + HostedZoneName: !Join + - '' + - - !Ref 'BaseDomain' + - . + RecordSets: + - AliasTarget: + DNSName: !Ref 'ALBDNSName' + EvaluateTargetHealth: 'false' + HostedZoneId: !Ref 'AlbCanonicalHostedZoneID' + Name: !Join + - '' + - - cumulus + - '-' + - cumulusdns + - . + - !Ref 'BaseDomain' + - . + SetIdentifier: original + Type: A + Weight: 1 + Type: AWS::Route53::RecordSetGroup + SgCidrAccessCidr100008ToASGPort22: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '22' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '22' + Type: AWS::EC2::SecurityGroupIngress + SgCidrAccessCidr100008ToASGPort8000: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '8000' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '8000' + Type: AWS::EC2::SecurityGroupIngress + SomeRoleName1: + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + - s3.amazonaws.com + Type: AWS::IAM::Role + asgwebsitesimple: + Properties: + AvailabilityZones: !Ref 'AvailabilityZones' + LaunchConfigurationName: !Ref 'lcwebsitesimple' + MaxSize: !Ref 'MaxSize' + MinSize: !Ref 'MinSize' + Tags: + - Key: Name + PropagateAtLaunch: true + Value: cumulustestscalinggroupInstanceProfilewebsitesimple + - Key: CE-OffHours + PropagateAtLaunch: true + Value: 'off' + TargetGroupARNs: + - !Ref 'cumulustesttargetgroup' + VPCZoneIdentifier: !Ref 'PrivateSubnets' + Type: AWS::AutoScaling::AutoScalingGroup + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: 'true' + AutoScalingRollingUpdate: + MaxBatchSize: '1' + MinInstancesInService: '1' + PauseTime: PT5M + WaitOnResourceSignals: 'true' + cumulustesttargetgroup: + Properties: + HealthCheckIntervalSeconds: '30' + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: '10' + HealthyThresholdCount: '4' + Matcher: + HttpCode: '200' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: '3' + VpcId: !Ref 'VpcId' + Type: AWS::ElasticLoadBalancingV2::TargetGroup + lcwebsitesimple: + Metadata: + AWS::CloudFormation::Init: + configSets: + default: + - install_and_run + install_and_run: + commands: + '01-startup': + command: nohup python -m SimpleHTTPServer 8000 & + Properties: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: '40' + IamInstanceProfile: !Ref 'InstanceProfilewebsitesimple' + ImageId: !FindInMap + - AmiMap + - !Ref 'AWS::Region' + - !Ref 'ImageName' + InstanceType: !Ref 'InstanceType' + KeyName: !Ref 'SshKeyName' + SecurityGroups: + - !Ref 'sg1websitesimple' + - !Ref 'sg2websitesimple' + UserData: !Base64 + Fn::Join: + - '' + - - "#!/bin/bash -xe\n" + - "yum update aws-cfn-bootstrap\n" + - "# Install the files and packages from the metadata\n" + - '/opt/aws/bin/cfn-init ' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --resource ' + - lcwebsitesimple + - ' --configsets default ' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + - "# Signal the ASG we are ready\n\n" + - /opt/aws/bin/cfn-signal -e 0 + - ' --resource websitesimple' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + Type: AWS::AutoScaling::LaunchConfiguration + sg1websitesimple: + Properties: + GroupDescription: sg1websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup + sg2websitesimple: + Properties: + GroupDescription: sg2websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup +Description: Acceptance Tests for cumulus scaling groups +Mappings: + AmiMap: + us-west-2: + amazonLinux2: ami-a9d09ed1 +Resources: + InstanceProfilewebsitesimple: + Properties: + Roles: + - !Ref 'SomeRoleName1' + Type: AWS::IAM::InstanceProfile + Route53Records: + Properties: + HostedZoneName: !Join + - '' + - - !Ref 'BaseDomain' + - . + RecordSets: + - AliasTarget: + DNSName: !Ref 'AlbDNSName' + EvaluateTargetHealth: 'false' + HostedZoneId: !Ref 'AlbCanonicalHostedZoneID' + Name: !Join + - '' + - - cumulus + - '-' + - cumulusdns + - . + - !Ref 'BaseDomain' + - . + SetIdentifier: original + Type: A + Weight: 1 + Type: AWS::Route53::RecordSetGroup + SgCidrAccessCidr100008ToASGPort22: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '22' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '22' + Type: AWS::EC2::SecurityGroupIngress + SgCidrAccessCidr100008ToASGPort8000: + Properties: + CidrIp: 10.0.0.0/8 + FromPort: '8000' + GroupId: !Ref 'sg1websitesimple' + IpProtocol: tcp + ToPort: '8000' + Type: AWS::EC2::SecurityGroupIngress + SomeRoleName1: + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + - s3.amazonaws.com + Type: AWS::IAM::Role + asgwebsitesimple: + Properties: + AvailabilityZones: !Ref 'AvailabilityZones' + LaunchConfigurationName: !Ref 'lcwebsitesimple' + MaxSize: !Ref 'MaxSize' + MinSize: !Ref 'MinSize' + Tags: + - Key: Name + PropagateAtLaunch: true + Value: cumulustestscalinggroupInstanceProfilewebsitesimple + - Key: CE-OffHours + PropagateAtLaunch: true + Value: 'off' + TargetGroupARNs: + - !Ref 'cumulustesttargetgroup' + VPCZoneIdentifier: !Ref 'PrivateSubnets' + Type: AWS::AutoScaling::AutoScalingGroup + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: 'true' + AutoScalingRollingUpdate: + MaxBatchSize: '1' + MinInstancesInService: '1' + PauseTime: PT5M + WaitOnResourceSignals: 'true' + cumulustesttargetgroup: + Properties: + HealthCheckIntervalSeconds: '30' + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: '10' + HealthyThresholdCount: '4' + Matcher: + HttpCode: '200' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: '3' + VpcId: !Ref 'VpcId' + Type: AWS::ElasticLoadBalancingV2::TargetGroup + lcwebsitesimple: + Metadata: + AWS::CloudFormation::Init: + configSets: + default: + - install_and_run + install_and_run: + commands: + '01-startup': + command: nohup python -m SimpleHTTPServer 8000 & + Properties: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: '40' + IamInstanceProfile: !Ref 'InstanceProfilewebsitesimple' + ImageId: !FindInMap + - AmiMap + - !Ref 'AWS::Region' + - !Ref 'ImageName' + InstanceType: !Ref 'InstanceType' + KeyName: !Ref 'SshKeyName' + SecurityGroups: + - !Ref 'sg1websitesimple' + - !Ref 'sg2websitesimple' + UserData: !Base64 + Fn::Join: + - '' + - - "#!/bin/bash -xe\n" + - "yum update aws-cfn-bootstrap\n" + - "# Install the files and packages from the metadata\n" + - '/opt/aws/bin/cfn-init ' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --resource ' + - lcwebsitesimple + - ' --configsets default ' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + - "# Signal the ASG we are ready\n\n" + - /opt/aws/bin/cfn-signal -e 0 + - ' --resource websitesimple' + - ' --stack ' + - !Ref 'AWS::StackName' + - ' --region ' + - !Ref 'AWS::Region' + - "\n" + Type: AWS::AutoScaling::LaunchConfiguration + sg1websitesimple: + Properties: + GroupDescription: sg1websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup + sg2websitesimple: + Properties: + GroupDescription: sg2websitesimple + VpcId: vpc-894b89ef + Type: AWS::EC2::SecurityGroup diff --git a/tests/stacker_test/stacker.yaml b/tests/stacker_test/stacker.yaml index 7446967..a556a9f 100644 --- a/tests/stacker_test/stacker.yaml +++ b/tests/stacker_test/stacker.yaml @@ -1,15 +1,56 @@ # This file is used in an integration test for this project. -namespace: cumulus-${namespace} +namespace: ${namespace} stacker_bucket: bswift-spike sys_path: . +vpc_variables: &vpc_variables + VpcId: ${VpcId} + AvailabilityZones: us-west-2a,us-west-2b + BaseDomain: ${BaseDomain} + PrivateSubnets: ${PrivateSubnets} + + # Common variables when using the ALB +alb_variables: &alb_variables + AlbDNSName: ${output ALB::DNSName} + AlbCanonicalHostedZoneID: ${output ALB::CanonicalHostedZoneID} + IAlbListener: ${output ALB::IAlbListener} + AlbSg: ${output ALB::InternalAlbSG} + +mappings: + AmiMap: + us-west-2: + amazonLinux2: ami-a9d09ed1 +# ami-31394949 + # The stacks key accepts a list of stack declarations. # http://stacker.readthedocs.io/en/latest/config.html#stacks stacks: # - name: s3bucket-smoke-test # class_path: blueprints.s3_bucket.S3Simple + - name: ALB + class_path: blueprints.alb.Alb + variables: + << : *vpc_variables # yaml parameter expansion syntax + ALBHostName: cumulus-alb-test + ALBCertName: ${ALBCertName} # INFO: This is a lookup to the .env file or -e cli param (which you won't use right!) - name: pipelinesimple class_path: blueprints.pipeline_simple.PipelineSimple + - name: scalinggroupsimple + class_path: blueprints.scaling_group_simple.ScalingGroupSimple + variables: + << : *vpc_variables # yaml parameter expansion syntax + ImageName: amazonLinux2 + SshKeyName: stc-admin-March-2017-PLAYPEN + - name: websitesimple + class_path: blueprints.website_simple.WebsiteSimple + variables: + << : *vpc_variables # yaml parameter expansion syntax + << : *alb_variables + namespace: ${namespace} + env: ${env} + ImageName: amazonLinux2 + SshKeyName: stc-admin-March-2017-PLAYPEN +