예제 #1
0
    def test_set_service_and_task_roles(self):
        service = ServiceConfiguration('test-service', 'test')

        try:
            service._validate_changes({
                'cloudlift_version': 'test',
                'ecr_repo': {
                    'name': 'test-service-repo'
                },
                'service_role_arn': 'foo',
                'services': {
                    'TestService': {
                        'memory_reservation': 1000,
                        'command': None,
                        'secrets_name': 'secret-config',
                        'http_interface': {
                            'internal': True,
                            'container_port': 8080,
                            'restrict_access_to': ['0.0.0.0/0'],
                        }
                    },
                    'task_arn': 'task_arn',
                    'task_execution_arn': 'task_execution_arn'
                }
            })
        except UnrecoverableException as e:
            self.fail('Exception thrown: {}'.format(e))
예제 #2
0
    def test_default_service_configuration_ecr_repo(self, getcwd):
        getcwd.return_value = "/path/to/dummy"

        service = ServiceConfiguration('test-service', 'test')
        conf = service._default_service_configuration()

        self.assertEqual({'name': 'dummy-repo'}, conf.get('ecr_repo'))
예제 #3
0
    def test_sidecars(self):
        service = ServiceConfiguration('test-service', 'test')

        try:
            service._validate_changes({
                'cloudlift_version': 'test',
                'ecr_repo': {
                    'name': 'test-service-repo'
                },
                'services': {
                    'TestService': {
                        'memory_reservation':
                        1000,
                        'command':
                        None,
                        'secrets_name':
                        'secret-config',
                        'sidecars': [{
                            'name': 'redis',
                            'image': 'redis:latest',
                            'memory_reservation': 128
                        }, {
                            'name': 'envoy',
                            'image': 'envoy:latest',
                            'memory_reservation': 256,
                            'command': ['./start']
                        }]
                    }
                }
            })
        except UnrecoverableException as e:
            self.fail('Exception thrown: {}'.format(e))
예제 #4
0
 def __init__(self, name, environment='', env_sample_file='', timeout_seconds=None, version=None,
              build_args=None, dockerfile=None, ssh=None, cache_from=None,
              deployment_identifier=None, working_dir='.'):
     self.name = name
     self.environment = environment
     self.deployment_identifier = deployment_identifier
     self.env_sample_file = env_sample_file
     self.timeout_seconds = timeout_seconds
     self.version = version
     self.ecr_client = boto3.session.Session(region_name=self.region).client('ecr')
     self.cluster_name = get_cluster_name(environment)
     self.service_configuration = ServiceConfiguration(service_name=name, environment=environment).get_config()
     self.service_info_fetcher = ServiceInformationFetcher(self.name, self.environment, self.service_configuration)
     if not self.service_info_fetcher.stack_found:
         raise UnrecoverableException(
             "error finding stack in ServiceUpdater: {}-{}".format(self.name, self.environment))
     ecr_repo_config = self.service_configuration.get('ecr_repo')
     self.ecr = ECR(
         self.region,
         ecr_repo_config.get('name', spinalcase(self.name + '-repo')),
         ecr_repo_config.get('account_id', get_account_id()),
         ecr_repo_config.get('assume_role_arn', None),
         version,
         build_args,
         dockerfile,
         working_dir,
         ssh,
         cache_from
     )
예제 #5
0
 def __init__(self, name, environment):
     self.name = name
     self.environment = environment
     self.stack_name = get_service_stack_name(environment, name)
     self.client = get_client_for('cloudformation', self.environment)
     self.environment_stack = self._get_environment_stack()
     self.existing_events = get_stack_events(self.client, self.stack_name)
     self.service_configuration = ServiceConfiguration(
         self.name, self.environment)
예제 #6
0
def get_version(name, environment, image, git):
    ServiceInformationFetcher(
        name,
        environment,
        ServiceConfiguration(service_name=name,
                             environment=environment).get_config(),
    ).get_version(print_image=image, print_git=git)
예제 #7
0
 def test_get_random_available_listener_rule_priority(self):
     testing_rules = set(
         list(range(1, 500)) + list(range(534, 678)) +
         list(range(1000, 30000)) + list(range(40000, 48000)))
     available_rules = set(range(1, 50001)) - testing_rules
     testing_listener_rules = [{"Priority": rule} for rule in testing_rules]
     for test_iteration in range(1, 100):
         assert ServiceConfiguration._get_random_available_listener_rule_priority(
             testing_listener_rules, "abcd") not in testing_rules
예제 #8
0
    def test_get_config(self):
        self.setup_existing_params()

        store_object = ServiceConfiguration('test-service', 'dummy-staging')
        response = store_object.get_config()
        assert response == {
            "services": {
                "TestService": {
                    "memory_reservation": 1000,
                    "command": None,
                    "http_interface": {
                        "internal": True,
                        "container_port": 80,
                        "restrict_access_to": [u'0.0.0.0/0']
                    }
                }
            }
        }
예제 #9
0
    def test_set_config_with_random_listner_priority(
            self, mock_get_listener_rule_priority):
        self.setup_params_for_listener_selection()
        mock_get_listener_rule_priority.return_value = 23
        store_object = ServiceConfiguration('test-service', 'dummy-staging')
        get_response = store_object.get_config()

        get_response["services"]["TestService"]["http_interface"][
            "restrict_access_to"] = [u"123.123.123.123/32"]
        store_object.set_config(get_response)
        update_response = store_object.get_config()

        expected_response = {
            'ecr_repo': {
                'name': 'test-service-repo'
            },
            'services': {
                'TestService': {
                    'memory_reservation': 1000,
                    'command': None,
                    'http_interface': {
                        'alb': {
                            'create_new': False,
                            'listener_arn': 'random-listener-arn',
                            'host': 'host-address.com',
                            'priority': 23,
                            'target_5xx_error_threshold': 5
                        },
                        'internal': True,
                        'container_port': 80,
                        'restrict_access_to': ['123.123.123.123/32']
                    }
                }
            }
        }
예제 #10
0
    def test_set_config_stop_timeout(self):
        self.setup_existing_params()

        store_object = ServiceConfiguration('test-service', 'dummy-staging')
        get_response = store_object.get_config()

        get_response["services"]["TestService"]["stop_timeout"] = 120
        store_object.set_config(get_response)
        update_response = store_object.get_config()

        assert update_response == {
            "ecr_repo": {
                "name": "test-service-repo"
            },
            "services": {
                "TestService": {
                    "memory_reservation": 1000,
                    'secrets_name': 'secret-config',
                    "command": None,
                    "http_interface": {
                        "internal": True,
                        "container_port": 80,
                        "restrict_access_to": [u'0.0.0.0/0'],
                    },
                    "stop_timeout": 120
                }
            }
        }
예제 #11
0
    def test_set_config(self):
        self.setup_existing_params()

        store_object = ServiceConfiguration('test-service', 'dummy-staging')
        get_response = store_object.get_config()

        get_response["services"]["TestService"]["http_interface"][
            "restrict_access_to"] = [u"123.123.123.123/32"]
        store_object.set_config(get_response)
        update_response = store_object.get_config()

        assert update_response == {
            'ecr_repo': {
                'name': 'test-service-repo'
            },
            "services": {
                "TestService": {
                    "memory_reservation": 1000,
                    'secrets_name': 'secret-config',
                    "command": None,
                    "http_interface": {
                        "internal": True,
                        "container_port": 80,
                        "restrict_access_to": [u"123.123.123.123/32"],
                    }
                }
            }
        }
 def test_initialization(self):
     with patch.object(ServiceConfiguration,
                       'get_config',
                       new=mocked_service_config):
         with patch.object(ServiceInformationFetcher,
                           'get_current_version',
                           new=mocked_service_information):
             service_config = ServiceConfiguration("test-service",
                                                   "staging")
             generator = ServiceTemplateGenerator(service_config, None)
             assert generator.env == 'staging'
             assert generator.application_name == 'test-service'
             assert generator.environment_stack == None
예제 #13
0
    def test_set_config_health_check_command(self):
        service = ServiceConfiguration('test-service', 'test')

        try:
            service._validate_changes({
                'cloudlift_version': 'test',
                'ecr_repo': {
                    'name': 'test-service-repo'
                },
                'services': {
                    'TestService': {
                        'memory_reservation': 1000,
                        'command': None,
                        'secrets_name': 'secret-config',
                        'http_interface': {
                            'internal': True,
                            'container_port': 8080,
                            'restrict_access_to': ['0.0.0.0/0'],
                        },
                        "container_health_check": {
                            "command": "echo 'Working'",
                            "start_period": 30,
                            "retries": 4,
                            "interval": 5,
                            "timeout": 30,
                        }
                    }
                }
            })
        except UnrecoverableException as e:
            self.fail('Exception thrown: {}'.format(e))

        try:
            service._validate_changes({
                'cloudlift_version': 'test',
                'ecr_repo': {
                    'name': 'test-service-repo'
                },
                'services': {
                    'TestService': {
                        'memory_reservation': 1000,
                        'command': None,
                        'secrets_name': 'secret-config',
                        'http_interface': {
                            'internal': True,
                            'container_port': 8080,
                            'restrict_access_to': ['0.0.0.0/0'],
                        },
                        "container_health_check": {
                            "start_period": 123,
                        }
                    }
                }
            })
            self.fail('Exception expected but did not fail')
        except UnrecoverableException as e:
            self.assertTrue(True)
예제 #14
0
    def test_set_config_placement_constraints(self):
        service = ServiceConfiguration('test-service', 'test')

        try:
            service._validate_changes({
                'cloudlift_version': 'test',
                'ecr_repo': {
                    'name': 'test-service-repo'
                },
                'services': {
                    'TestService': {
                        'memory_reservation':
                        1000,
                        'secrets_name':
                        'secret-config',
                        'command':
                        None,
                        'placement_constraints': [{
                            'type': 'memberOf',
                            'expression': 'expr'
                        }]
                    }
                }
            })
        except UnrecoverableException as e:
            self.fail('Exception thrown: {}'.format(e))

        try:
            service._validate_changes({
                'cloudlift_version': 'test',
                'ecr_repo': {
                    'name': 'test-service-repo'
                },
                'services': {
                    'TestService': {
                        'memory_reservation': 1000,
                        'secrets_name': 'secret-config',
                        'command': None,
                        'placement_constraints': [{
                            'type': 'invalid'
                        }]
                    }
                }
            })
            self.fail('Validation error expected but validation passed')
        except UnrecoverableException as e:
            self.assertTrue(
                "'invalid' is not one of ['memberOf', 'distinctInstance']" in
                str(e))
예제 #15
0
    def test_set_config_system_controls(self):
        service = ServiceConfiguration('test-service', 'test')

        try:
            service._validate_changes({
                'cloudlift_version': 'test',
                'ecr_repo': {
                    'name': 'test-service-repo'
                },
                'services': {
                    'TestService': {
                        'memory_reservation': 1000,
                        'command': None,
                        'secrets_name': 'secret-config',
                        'system_controls': [{
                            'namespace': 'ns',
                            'value': 'val'
                        }]
                    }
                }
            })
        except UnrecoverableException as e:
            self.fail('Exception thrown: {}'.format(e))

        try:
            service._validate_changes({
                'cloudlift_version': 'test',
                'ecr_repo': {
                    'name': 'test-service-repo'
                },
                'services': {
                    'TestService': {
                        'memory_reservation': 1000,
                        'command': None,
                        'secrets_name': 'secret-config',
                        'system_controls': "invalid"
                    }
                }
            })
            self.fail('Validation error expected but validation passed')
        except UnrecoverableException as e:
            self.assertTrue("'invalid' is not of type 'array'" in str(e))
예제 #16
0
    def test_container_labels(self):
        service = ServiceConfiguration('test-service', 'test')

        with self.assertRaises(UnrecoverableException) as error:
            service._validate_changes({
                'cloudlift_version': 'test',
                'ecr_repo': {
                    'name': 'test-service-repo'
                },
                'services': {
                    'TestService': {
                        'memory_reservation': 1000,
                        'command': None,
                        'secrets_name': 'secret-config',
                        "container_labels": {
                            "key": 1
                        }
                    }
                }
            })

        self.assertEqual(
            "1 is not of type 'string' in services.TestService.container_labels.key",
            error.exception.value)

        service._validate_changes({
            'cloudlift_version': 'test',
            'ecr_repo': {
                'name': 'test-service-repo'
            },
            'services': {
                'TestService': {
                    'memory_reservation': 1000,
                    'command': None,
                    'secrets_name': 'secret-config',
                    "container_labels": {
                        "key": "value"
                    }
                }
            }
        })
예제 #17
0
def verify_env_sample(name, environment, env_sample_directory_path):
    ServiceInformationFetcher(
        name,
        environment,
        ServiceConfiguration(service_name=name, environment=environment).get_config(),
    ).verify_env_sample(env_sample_directory_path)
    def test_generate_service(self):
        environment = 'staging'
        application_name = 'dummy'
        env_stack = {
            u'StackId':
            'arn:aws:cloudformation:ap-south-1:725827686899:stack/cluster-staging/65410f80-d21c-11e8-913a-503a56826a2a',
            u'LastUpdatedTime':
            datetime.datetime(2018, 11, 9, 5, 22, 30, 691000),
            u'Parameters': [{
                u'ParameterValue': 'staging-cluster-v3',
                u'ParameterKey': 'KeyPair'
            }, {
                u'ParameterValue': '1',
                u'ParameterKey': 'ClusterSize'
            }, {
                u'ParameterValue':
                'arn:aws:sns:ap-south-1:725827686899:non-prod-mumbai',
                u'ParameterKey': 'NotificationSnsArn'
            }, {
                u'ParameterValue': 'staging',
                u'ParameterKey': 'Environment'
            }, {
                u'ParameterValue': 'm5.xlarge',
                u'ParameterKey': 'InstanceType'
            }, {
                u'ParameterValue': '50',
                u'ParameterKey': 'MaxSize'
            }],
            u'Tags': [],
            u'Outputs': [{
                u'Description': 'VPC in which environment is setup',
                u'OutputKey': 'VPC',
                u'OutputValue': 'vpc-00f07c5a6b6c9abdb'
            }, {
                u'Description':
                'Options used with cloudlift when building this cluster',
                u'OutputKey':
                'CloudliftOptions',
                u'OutputValue':
                '{"min_instances": 1, "key_name": "staging-cluster-v3", "max_instances": 50, "instance_type": "m5.xlarge", "cloudlift_version": "0.9.4", "env": "staging"}'
            }, {
                u'Description': 'Maximum instances in cluster',
                u'OutputKey': 'MaxInstances',
                u'OutputValue': '50'
            }, {
                u'Description': 'Security group ID for ALB',
                u'OutputKey': 'SecurityGroupAlb',
                u'OutputValue': 'sg-095dbeb511019cfd8'
            }, {
                u'Description': 'Key Pair name for accessing the instances',
                u'OutputKey': 'KeyName',
                u'OutputValue': 'staging-cluster-v3'
            }, {
                u'Description': 'ID of the 1st subnet',
                u'OutputKey': 'PrivateSubnet1',
                u'OutputValue': 'subnet-09b6cd23af94861cc'
            }, {
                u'Description': 'ID of the 2nd subnet',
                u'OutputKey': 'PrivateSubnet2',
                u'OutputValue': 'subnet-0657bc2faa99ce5f7'
            }, {
                u'Description': 'Minimum instances in cluster',
                u'OutputKey': 'MinInstances',
                u'OutputValue': '1'
            }, {
                u'Description': 'ID of the 2nd subnet',
                u'OutputKey': 'PublicSubnet2',
                u'OutputValue': 'subnet-096377a44ccb73aca'
            }, {
                u'Description': 'EC2 instance type',
                u'OutputKey': 'InstanceType',
                u'OutputValue': 'm5.xlarge'
            }, {
                u'Description': 'ID of the 1st subnet',
                u'OutputKey': 'PublicSubnet1',
                u'OutputValue': 'subnet-0aeae8fe5e13a7ff7'
            }, {
                u'Description': 'The name of the stack',
                u'OutputKey': 'StackName',
                u'OutputValue': 'cluster-staging'
            }, {
                u'Description':
                'The unique ID of the stack. To be supplied to circle CI environment variables to validate during deployment.',
                u'OutputKey':
                'StackId',
                u'OutputValue':
                'arn:aws:cloudformation:ap-south-1:725827686899:stack/cluster-staging/65410f80-d21c-11e8-913a-503a56826a2a'
            }],
            u'CreationTime':
            datetime.datetime(2018, 10, 17, 14, 53, 23, 469000),
            u'Capabilities': ['CAPABILITY_NAMED_IAM'],
            u'StackName':
            'cluster-staging',
            u'NotificationARNs': [],
            u'StackStatus':
            'UPDATE_COMPLETE',
            u'DisableRollback':
            True,
            u'ChangeSetId':
            'arn:aws:cloudformation:ap-south-1:725827686899:changeSet/cg901a2f5dbf984b9e9807a21da1ac7d12/7588cd05-1e2d-4dd6-85ab-12b921baa814',
            u'RollbackConfiguration': {}
        }

        with patch.object(ServiceConfiguration,
                          'get_config',
                          new=mocked_service_config):
            with patch.object(ParameterStore,
                              'get_existing_config',
                              new=mocked_environment_config):
                with patch.object(ServiceInformationFetcher,
                                  'get_current_version',
                                  new=mocked_service_information):
                    service_config = ServiceConfiguration(
                        application_name, environment)
                    template_generator = ServiceTemplateGenerator(
                        service_config, env_stack)
                    template_generator.env_sample_file_path = './test/templates/test_env.sample'
                    generated_template = template_generator.generate_service()

        assert to_json(''.join(
            open('./test/templates/expected_service_template.yml').readlines())
                       ) == to_json(generated_template)
예제 #19
0
class ServiceUpdater(object):
    def __init__(self,
                 name,
                 environment='',
                 env_sample_file='',
                 timeout_seconds=None,
                 version=None,
                 build_args=None,
                 dockerfile=None,
                 ssh=None,
                 cache_from=None,
                 deployment_identifier=None,
                 working_dir='.'):
        self.name = name
        self.environment = environment
        self.deployment_identifier = deployment_identifier
        self.env_sample_file = env_sample_file
        self.timeout_seconds = timeout_seconds
        self.version = version
        self.ecr_client = boto3.session.Session(
            region_name=self.region).client('ecr')
        self.cluster_name = get_cluster_name(environment)
        self.service_configuration = ServiceConfiguration(
            service_name=name, environment=environment).get_config()
        self.service_info_fetcher = ServiceInformationFetcher(
            self.name, self.environment, self.service_configuration)
        if not self.service_info_fetcher.stack_found:
            raise UnrecoverableException(
                "error finding stack in ServiceUpdater: {}-{}".format(
                    self.name, self.environment))
        ecr_repo_config = self.service_configuration.get('ecr_repo')
        self.ecr = ECR(
            self.region,
            ecr_repo_config.get('name', spinalcase(self.name + '-repo')),
            ecr_repo_config.get('account_id', get_account_id()),
            ecr_repo_config.get('assume_role_arn', None), version, build_args,
            dockerfile, working_dir, ssh, cache_from)

    def run(self):
        log_warning("Deploying to {self.region}".format(**locals()))
        if not os.path.exists(self.env_sample_file):
            raise UnrecoverableException('env.sample not found. Exiting.')
        log_intent("name: " + self.name + " | environment: " +
                   self.environment + " | version: " + str(self.version) +
                   " | deployment_identifier: " +
                   str(self.deployment_identifier))
        log_bold("Checking image in ECR")
        self.ecr.upload_artefacts()
        log_bold("Initiating deployment\n")

        image_url = self.ecr.image_uri
        target = deployer.deploy_new_version
        kwargs = dict(
            cluster_name=self.cluster_name,
            service_name=self.name,
            sample_env_file_path=self.env_sample_file,
            timeout_seconds=self.timeout_seconds,
            env_name=self.environment,
            ecr_image_uri=image_url,
            deployment_identifier=self.deployment_identifier,
        )
        self.run_job_for_all_services("Deploy", target, kwargs)

    def revert(self):
        target = deployer.revert_deployment
        kwargs = dict(cluster_name=self.cluster_name,
                      timeout_seconds=self.timeout_seconds,
                      deployment_identifier=self.deployment_identifier)
        self.run_job_for_all_services("Revert", target, kwargs)

    def upload_to_ecr(self, additional_tags):
        self.ecr.upload_artefacts()
        self.ecr.add_tags(additional_tags)

    def run_job_for_all_services(self, job_name, target, kwargs):
        log_bold("{} concurrency: {}".format(job_name, DEPLOYMENT_CONCURRENCY))
        jobs = []
        service_info = self.service_info_fetcher.service_info
        for index, ecs_service_logical_name in enumerate(service_info):
            ecs_service_info = service_info[ecs_service_logical_name]
            log_bold(f"Queueing {job_name} of " +
                     ecs_service_info['ecs_service_name'])
            color = DEPLOYMENT_COLORS[index % 3]
            services_configuration = self.service_configuration['services']
            kwargs.update(
                dict(
                    ecs_service_name=ecs_service_info['ecs_service_name'],
                    secrets_name=ecs_service_info.get('secrets_name'),
                    ecs_service_logical_name=ecs_service_logical_name,
                    color=color,
                    service_configuration=services_configuration.get(
                        ecs_service_logical_name),
                    region=self.region,
                ))
            process = multiprocessing.Process(target=target, kwargs=kwargs)
            jobs.append(process)
        all_exit_codes = []
        for chunk_of_jobs in chunks(jobs, DEPLOYMENT_CONCURRENCY):
            for process in chunk_of_jobs:
                process.start()

            while True:
                sleep(1)
                exit_codes = [proc.exitcode for proc in chunk_of_jobs]
                if None not in exit_codes:
                    break

            for exit_code in exit_codes:
                all_exit_codes.append(exit_code)
        if any(all_exit_codes) != 0:
            raise UnrecoverableException(f"{job_name} failed")

    @property
    def region(self):
        return get_region_for_environment(self.environment)
예제 #20
0
class ServiceCreator(object):
    '''
        Create and execute CloudFormation template or changeset for existing
        CloudFormation template for ECS service and related dependencies
    '''
    def __init__(self, name, environment):
        self.name = name
        self.environment = environment
        self.stack_name = get_service_stack_name(environment, name)
        self.client = get_client_for('cloudformation', self.environment)
        self.environment_stack = self._get_environment_stack()
        self.existing_events = get_stack_events(self.client, self.stack_name)
        self.service_configuration = ServiceConfiguration(
            self.name, self.environment)

    def create(self):
        '''
            Create and execute CloudFormation template for ECS service
            and related dependencies
        '''
        log_bold("Initiating service creation")
        self.service_configuration.edit_config()

        template_generator = ServiceTemplateGenerator(
            self.service_configuration, self.environment_stack)
        service_template_body = template_generator.generate_service()

        try:
            self.client.create_stack(
                StackName=self.stack_name,
                TemplateBody=service_template_body,
                Parameters=[{
                    'ParameterKey': 'Environment',
                    'ParameterValue': self.environment,
                }],
                OnFailure='DO_NOTHING',
                Capabilities=['CAPABILITY_NAMED_IAM'],
            )
            log_bold("Submitted to cloudformation. Checking progress...")
            self._print_progress()
        except ClientError as boto_client_error:
            error_code = boto_client_error.response['Error']['Code']
            if error_code == 'AlreadyExistsException':
                log_err("Stack " + self.stack_name + " already exists.")
                exit(1)
            else:
                raise boto_client_error

    def update(self):
        '''
            Create and execute changeset for existing CloudFormation template
            for ECS service and related dependencies
        '''

        log_bold("Starting to update service")
        self.service_configuration.edit_config()
        try:
            template_generator = ServiceTemplateGenerator(
                self.service_configuration, self.environment_stack)
            service_template_body = template_generator.generate_service()
            change_set = create_change_set(self.client, service_template_body,
                                           self.stack_name, "",
                                           self.environment)
            self.service_configuration.update_cloudlift_version()
            log_bold("Executing changeset. Checking progress...")
            self.client.execute_change_set(
                ChangeSetName=change_set['ChangeSetId'])
            self._print_progress()
        except ClientError as exc:
            if "No updates are to be performed." in str(exc):
                log_err("No updates are to be performed")
            else:
                raise exc

    def _get_environment_stack(self):
        try:
            log("Looking for " + self.environment + " cluster.")
            environment_stack = self.client.describe_stacks(
                StackName=get_cluster_name(self.environment))['Stacks'][0]
            log_bold(self.environment + " stack found. Using stack with ID: " +
                     environment_stack['StackId'])
        except ClientError:
            log_err(self.environment +
                    " cluster not found. Create the environment \
cluster using `create_environment` command.")
            exit(1)
        return environment_stack

    def _print_progress(self):
        while True:
            response = self.client.describe_stacks(StackName=self.stack_name)
            if "IN_PROGRESS" not in response['Stacks'][0]['StackStatus']:
                break
            all_events = get_stack_events(self.client, self.stack_name)
            print_new_events(all_events, self.existing_events)
            self.existing_events = all_events
            sleep(5)
        final_status = response['Stacks'][0]['StackStatus']
        if "FAIL" in final_status:
            log_err("Finished with status: %s" % (final_status))
        else:
            log_bold("Finished with status: %s" % (final_status))
예제 #21
0
class ServiceCreator(object):
    '''
        Create and execute CloudFormation template or changeset for existing
        CloudFormation template for ECS service and related dependencies
    '''
    def __init__(self, name, environment, env_sample_file):
        self.name = name
        self.environment = environment
        self.stack_name = get_service_stack_name(environment, name)
        self.client = get_client_for('cloudformation', self.environment)
        self.environment_stack = self._get_environment_stack()
        self.existing_events = get_stack_events(self.client, self.stack_name)
        self.service_configuration = ServiceConfiguration(
            self.name, self.environment)
        self.env_sample_file = env_sample_file

    def create(self,
               config_body=None,
               version=None,
               build_arg=None,
               dockerfile=None,
               ssh=None,
               cache_from=None):
        '''
            Create and execute CloudFormation template for ECS service
            and related dependencies
        '''
        log_bold("Initiating service creation")
        if config_body is None:
            self.service_configuration.edit_config()
        else:
            self.service_configuration.set_config(config_body)

        self.service_configuration.validate()
        ecr_repo_config = self.service_configuration.get_config().get(
            'ecr_repo')
        ecr = ECR(
            region=get_region_for_environment(self.environment),
            repo_name=ecr_repo_config.get('name'),
            account_id=ecr_repo_config.get('account_id', None),
            assume_role_arn=ecr_repo_config.get('assume_role_arn', None),
            version=version,
            build_args=build_arg,
            dockerfile=dockerfile,
            ssh=ssh,
            cache_from=cache_from,
        )
        ecr.upload_artefacts()

        template_generator = ServiceTemplateGenerator(
            self.service_configuration, self.environment_stack,
            self.env_sample_file, ecr.image_uri)
        service_template_body = template_generator.generate_service()

        try:
            options = prepare_stack_options_for_template(
                service_template_body, self.environment, self.stack_name)
            self.client.create_stack(
                StackName=self.stack_name,
                Parameters=[{
                    'ParameterKey': 'Environment',
                    'ParameterValue': self.environment,
                }],
                OnFailure='DO_NOTHING',
                Capabilities=['CAPABILITY_NAMED_IAM'],
                **options,
            )
            log_bold("Submitted to cloudformation. Checking progress...")
            self._print_progress()
        except ClientError as boto_client_error:
            error_code = boto_client_error.response['Error']['Code']
            if error_code == 'AlreadyExistsException':
                raise UnrecoverableException("Stack " + self.stack_name +
                                             " already exists.")
            else:
                raise boto_client_error

    def update(self):
        '''
            Create and execute changeset for existing CloudFormation template
            for ECS service and related dependencies
        '''

        log_bold("Starting to update service")
        self.service_configuration.edit_config()
        self.service_configuration.validate()

        information_fetcher = ServiceInformationFetcher(
            self.service_configuration.service_name,
            self.environment,
            self.service_configuration.get_config(),
        )
        try:
            current_image_uri = information_fetcher.get_current_image_uri()
            desired_counts = information_fetcher.fetch_current_desired_count()
            current_deployment_identifier = information_fetcher.get_current_deployment_identifier(
            )

            template_generator = ServiceTemplateGenerator(
                self.service_configuration, self.environment_stack,
                self.env_sample_file, current_image_uri, desired_counts,
                current_deployment_identifier)
            service_template_body = template_generator.generate_service()
            change_set = create_change_set(self.client, service_template_body,
                                           self.stack_name, None,
                                           self.environment)
            if change_set is None:
                return
            self.service_configuration.update_cloudlift_version()
            log_bold("Executing changeset. Checking progress...")
            self.client.execute_change_set(
                ChangeSetName=change_set['ChangeSetId'])
            self._print_progress()
        except ClientError as exc:
            if "No updates are to be performed." in str(exc):
                log_err("No updates are to be performed")
            else:
                raise exc

    def _get_environment_stack(self):
        try:
            log("Looking for " + self.environment + " cluster.")
            environment_stack = self.client.describe_stacks(
                StackName=get_cluster_name(self.environment))['Stacks'][0]
            log_bold(self.environment + " stack found. Using stack with ID: " +
                     environment_stack['StackId'])
        except ClientError:
            raise UnrecoverableException(
                self.environment +
                " cluster not found. Create the environment \
cluster using `create_environment` command.")
        return environment_stack

    def _print_progress(self):
        while True:
            response = self.client.describe_stacks(StackName=self.stack_name)
            if "IN_PROGRESS" not in response['Stacks'][0]['StackStatus']:
                break
            all_events = get_stack_events(self.client, self.stack_name)
            print_new_events(all_events, self.existing_events)
            self.existing_events = all_events
            sleep(5)
        final_status = response['Stacks'][0]['StackStatus']
        if "FAIL" in final_status:
            log_err("Finished with status: %s" % (final_status))
        else:
            log_bold("Finished with status: %s" % (final_status))
예제 #22
0
 def test_initialization(self):
     store_object = ServiceConfiguration('test-service', 'dummy-staging')
     assert store_object.environment == 'dummy-staging'
     assert store_object.service_name == 'test-service'
     assert store_object.table is not None