Example #1
0
 def _get_table(self):
     dynamodb_client = boto3.session.Session().client('dynamodb')
     table_names = dynamodb_client.list_tables()['TableNames']
     if self.table_name not in table_names:
         log_warning("Could not find configuration table, creating one..")
         self._create_configuration_table()
     return self.dynamodb.Table(self.table_name)
 def _create_config(self):
     log_warning(
         "\nConfiguration for this environment was not found in DynamoDB.\
         \nInitiating prompts for setting up configuration.\
         \nIf this environment was previously configured, please ensure\
         \ndefault region is the same as previously used. Otherwise, use\
         \nthe same configuration.\n"
     )
     region = prompt("AWS region for environment", default='ap-south-1')
     vpc_config = self._create_vpc_config()
     cluster_min_instances = prompt("Min instances in cluster", default=1)
     cluster_max_instances = prompt("Max instances in cluster", default=5)
     cluster_instance_type = prompt("Instance type", default='m5.xlarge')
     key_name = prompt("SSH key name")
     notifications_arn = prompt("Notification SNS ARN")
     ssl_certificate_arn = prompt("SSL certificate ARN")
     environment_configuration = {
         self.environment: {
             "region": region,
             "vpc": vpc_config,
             "cluster": {
                 "min_instances": cluster_min_instances,
                 "max_instances": cluster_max_instances,
                 "instance_type": cluster_instance_type,
                 "key_name": key_name
             },
             "environment": {
                 "notifications_arn": notifications_arn,
                 "ssl_certificate_arn": ssl_certificate_arn
             }
         }
     }
     self._set_config(environment_configuration)
     pass
    def create(self):
        log_warning(
            "Create task definition to {self.region}".format(**locals()))
        if not os.path.exists(self.env_sample_file):
            raise UnrecoverableException('env.sample not found. Exiting.')
        ecr_client = EcrClient(self.name, self.region, self.build_args)
        ecr_client.set_version(self.version)
        log_intent("name: " + self.name + " | environment: " +
                   self.environment + " | version: " + str(ecr_client.version))
        log_bold("Checking image in ECR")
        ecr_client.build_and_upload_image()
        log_bold("Creating task definition\n")
        env_config = build_config(self.environment, self.name,
                                  self.env_sample_file)
        container_definition_arguments = {
            "environment": [{
                "name": k,
                "value": v
            } for (k, v) in env_config],
            "name": pascalcase(self.name) + "Container",
            "image": _complete_image_url(ecr_client),
            "essential": True,
            "logConfiguration": self._gen_log_config(pascalcase(self.name)),
            "memoryReservation": 1024
        }

        ecs_client = EcsClient(region=self.region)
        ecs_client.register_task_definition(self._task_defn_family(),
                                            [container_definition_arguments],
                                            [], None)
        log_bold("Task definition successfully created\n")
Example #4
0
 def set_version(self, version):
     if version:
         try:
             commit_sha = self._find_commit_sha(version)
         except:
             commit_sha = version
         log_intent("Using commit hash " + commit_sha + " to find image")
         image = self._find_image_in_ecr(commit_sha)
         if not image:
             log_warning("Please build, tag and upload the image for the \
     commit " + commit_sha)
             raise UnrecoverableException(
                 "Image for given version could not be found.")
         self.version = version
     else:
         dirty = subprocess.check_output(["git", "status",
                                          "--short"]).decode("utf-8")
         if dirty:
             self.version = 'dirty'
             log_intent("Version parameter was not provided. Determined \
     version to be " + self.version + " based on current status")
         else:
             self.version = self._find_commit_sha()
             log_intent("Version parameter was not provided. Determined \
     version to be " + self.version + " based on current status")
Example #5
0
def build_secrets_for_all_namespaces(env_name, service_name, ecs_service_name,
                                     sample_env_folder_path, secrets_name):
    secrets_across_namespaces = {}
    namespaces = get_namespaces_from_directory(sample_env_folder_path)
    duplicates = find_duplicate_keys(sample_env_folder_path, namespaces)
    if len(duplicates) != 0:
        raise UnrecoverableException(
            'duplicate keys found in env sample files {} '.format(duplicates))
    for namespace in namespaces:
        secrets_for_namespace = _get_secrets_for_namespace(
            env_name, namespace, sample_env_folder_path, secrets_name)
        secrets_across_namespaces.update(secrets_for_namespace)

    automated_secret_name = get_automated_injected_secret_name(
        env_name, service_name, ecs_service_name)
    existing_secrets = {}
    try:
        existing_secrets = secrets_manager.get_config(automated_secret_name,
                                                      env_name)['secrets']
    except Exception as err:
        log_warning(
            f'secret {automated_secret_name} does not exist. It will be created: {err}'
        )
    if existing_secrets != secrets_across_namespaces:
        log(f"Updating {automated_secret_name}")
        secrets_manager.set_secrets_manager_config(env_name,
                                                   automated_secret_name,
                                                   secrets_across_namespaces)
    arn = secrets_manager.get_config(automated_secret_name, env_name)['ARN']
    return dict(CLOUDLIFT_INJECTED_SECRETS=arn)
 def _get_table(self):
     table_names = self.dynamodb_client.list_tables()['TableNames']
     if self.table_name not in table_names:
         log_warning("Could not find {} table, creating one..".format(
             self.table_name))
         self._create_configuration_table()
         self._table_status()
     return self.dynamodb.Table(self.table_name)
Example #7
0
 def check_consistency(secret_id, expected_configuration):
     try:
         r = client.get_secret_value(SecretId=secret_id)
         value = json.loads(r['SecretString'])
         return expected_configuration == value
     except Exception as e:
         log_warning("secrets_manager consistency failure: {}".format(e))
         return False
Example #8
0
    def get_current_version(self):
        commit_sha = self._fetch_current_task_definition_tag()
        if commit_sha is None or commit_sha == 'dirty':
            log_warning(
                "Currently deployed tag could not be found or is dirty,\
resetting to master")
            commit_sha = "master"
        return commit_sha
Example #9
0
 def init_stack_info(self):
     self.stack_name = get_service_stack_name(self.environment, self.name)
     try:
         stack = get_client_for('cloudformation',
                                self.environment).describe_stacks(
                                    StackName=self.stack_name)['Stacks'][0]
         service_name_list = list(
             filter(lambda x: x['OutputKey'].endswith('EcsServiceName'),
                    stack['Outputs']))
         self.ecs_service_names = [
             svc_name['OutputValue'] for svc_name in service_name_list
         ]
     except Exception:
         self.ecs_service_names = []
         log_warning("Could not determine services.")
Example #10
0
    def ensure_image_in_ecr(self):
        if self.version:
            try:
                commit_sha = self._find_commit_sha(self.version)
            except:
                commit_sha = self.version
            log_intent("Using commit hash " + commit_sha + " to find image")
            image = self._find_image_in_ecr(commit_sha)
            if not image:
                log_warning("Please build, tag and upload the image for the \
commit " + commit_sha)
                raise UnrecoverableException("Image for given version could not be found.")
        else:
            dirty = subprocess.check_output(
                ["git", "status", "--short"]
            ).decode("utf-8")
            if dirty:
                self.version = 'dirty'
                log_intent("Version parameter was not provided. Determined \
version to be " + self.version + " based on current status")
                image = None
            else:
                self.version = self._find_commit_sha()
                log_intent("Version parameter was not provided. Determined \
version to be " + self.version + " based on current status")
                image = self._find_image_in_ecr(self.version)

            if image:
                log_intent("Image found in ECR")
            else:
                log_bold("Image not found in ECR. Building image")
                image_name = spinalcase(self.name) + ':' + self.version
                ecr_name = self.ecr_image_uri + ':' + self.version
                self._build_image(image_name)
                self._push_image(image_name, ecr_name)
                image = self._find_image_in_ecr(self.version)

        try:
            image_manifest = image['imageManifest']
            self.ecr_client.put_image(
                repositoryName=self.repo_name,
                imageTag=self.version,
                imageManifest=image_manifest
            )
        except Exception:
            pass
Example #11
0
    def run(self):
        log_warning("Deploying to {self.region}".format(**locals()))
        self.init_stack_info()
        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))
        log_bold("Checking image in ECR")
        self.upload_artefacts()
        log_bold("Initiating deployment\n")
        ecs_client = EcsClient(None, None, self.region)

        jobs = []
        for index, service_name in enumerate(self.ecs_service_names):
            log_bold("Starting to deploy " + service_name)
            color = DEPLOYMENT_COLORS[index % 3]
            image_url = self.ecr_image_uri
            image_url += (':' + self.version)
            process = multiprocessing.Process(
                target=deployer.deploy_new_version,
                args=(
                    ecs_client,
                    self.cluster_name,
                    service_name,
                    self.version,
                    self.name,
                    self.env_sample_file,
                    self.environment,
                    color,
                    image_url
                )
            )
            jobs.append(process)
            process.start()

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

        if any(exit_codes) != 0:
            raise UnrecoverableException("Deployment failed")
 def update(self):
     log_warning(
         "Update task definition to {self.region}".format(**locals()))
     if not os.path.exists(self.env_sample_file):
         raise UnrecoverableException('env.sample not found. Exiting.')
     ecr_client = EcrClient(self.name, self.region, self.build_args)
     ecr_client.set_version(self.version)
     log_intent("name: " + self.name + " | environment: " +
                self.environment + " | version: " + str(ecr_client.version))
     log_bold("Checking image in ECR")
     ecr_client.build_and_upload_image()
     log_bold("Updating task definition\n")
     env_config = build_config(self.environment, self.name,
                               self.env_sample_file)
     ecs_client = EcsClient(region=self.region)
     deployment = DeployAction(ecs_client, self.cluster_name, None)
     task_defn = self._apply_changes_over_current_task_defn(
         env_config, ecs_client, ecr_client, deployment)
     deployment.update_task_definition(task_defn)
     log_bold("Task definition successfully updated\n")
Example #13
0
    def ensure_image_in_ecr(self):
        if self.version:
            log_intent("Using commit hash " + self.version + " to find image")
            image = self._find_image_in_ecr(self.version)
            if not image:
                log_warning("Please build, tag and upload the image for the \
commit " + self.version)
                raise UnrecoverableException(
                    "Image for given version could not be found.")
        else:
            dirty = subprocess.check_output(["git", "status",
                                             "--short"]).decode("utf-8")
            if dirty:
                log_intent(
                    "Repository has uncommitted changes. Marking version as dirty."
                )
                self.version = '{}-dirty'.format(self._derive_version())
                image = None
            else:
                self.version = self._derive_version()
                image = self._find_image_in_ecr(self.version)

            log_intent(
                "Version parameter was not provided. Determined version to be "
                + self.version + " based on current status")
            if image:
                log_intent("Image found in ECR")
            else:
                log_bold("Image not found in ECR. Building image")
                self._build_image()
                self._push_image()
                image = self._find_image_in_ecr(self.version)
        try:
            image_manifest = image['imageManifest']
            self.client.put_image(repositoryName=self.repo_name,
                                  imageTag=self.version,
                                  imageManifest=image_manifest)
        except Exception:
            pass
        self._add_image_tag(self.version,
                            f'{self.version}-{self._git_epoch_time()}')
Example #14
0
    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: " + self.deployment_identifier)
        log_bold("Checking image in ECR")
        self.ecr.upload_artefacts()
        log_bold("Initiating deployment\n")
        ecs_client = EcsClient(None, None, self.region)

        image_url = self.ecr.image_uri
        target = deployer.deploy_new_version
        kwargs = dict(client=ecs_client, 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)
Example #15
0
    def _edit_config(self):
        '''
            Open editor to update configuration
        '''

        try:
            current_configuration = self.get_config()

            updated_configuration = edit(
                json.dumps(current_configuration,
                           indent=4,
                           sort_keys=True,
                           cls=DecimalEncoder))

            if updated_configuration is None:
                log_warning("No changes made.")
            else:
                updated_configuration = json.loads(updated_configuration)
                differences = list(
                    dictdiffer.diff(current_configuration,
                                    updated_configuration))
                if not differences:
                    log_warning("No changes made.")
                else:
                    print_json_changes(differences)
                    if confirm('Do you want update the config?'):
                        self._set_config(updated_configuration)
                    else:
                        log_warning("Changes aborted.")
        except ClientError:
            log_err("Unable to fetch environment configuration from DynamoDB.")
            exit(1)
    def init_stack_info(self):
        stack_name = get_service_stack_name(self.environment, self.name)
        try:
            stack = self.cfn_client.describe_stacks(
                StackName=stack_name)['Stacks'][0]
            self.stack_found = True

            stack_outputs = {
                output['OutputKey']: output['OutputValue']
                for output in stack['Outputs']
            }

            for service_name, service_config in self.service_configuration.get(
                    'services', {}).items():
                service_metadata = dict()

                if "ecs_service_name" in service_config:
                    service_metadata["ecs_service_name"] = service_config.get(
                        'ecs_service_name')
                else:
                    service_metadata["ecs_service_name"] = stack_outputs.get(
                        f'{service_name}EcsServiceName')

                service_metadata["secrets_name"] = service_config.get(
                    'secrets_name', None)

                self.service_info[service_name] = service_metadata

            self.listener_rules = [
                resource_summary
                for resource_summary in (self._get_stack_resource_summaries())
                if resource_summary['LogicalResourceId'].endswith(
                    'ListenerRule')
            ]
        except Exception as e:
            self.stack_found = False
            log_warning(
                "Could not determine services. Stack not found. Error raised: {}"
                .format(e))
Example #17
0
def build_secrets_for_all_namespaces(env_name, service_name, ecs_service_name,
                                     sample_env_folder_path, secrets_name):
    secrets_across_namespaces = verify_and_get_secrets_for_all_namespaces(
        env_name, sample_env_folder_path, secrets_name)

    automated_secret_name = get_automated_injected_secret_name(
        env_name, service_name, ecs_service_name)
    existing_secrets = {}
    try:
        existing_secrets = secrets_manager.get_config(automated_secret_name,
                                                      env_name)['secrets']
    except Exception as err:
        log_warning(
            f'secret {automated_secret_name} does not exist. It will be created: {err}'
        )
    if existing_secrets != secrets_across_namespaces:
        log(f"Updating {automated_secret_name}")
        secrets_manager.set_secrets_manager_config(env_name,
                                                   automated_secret_name,
                                                   secrets_across_namespaces)
    arn = secrets_manager.get_config(automated_secret_name, env_name)['ARN']
    return dict(CLOUDLIFT_INJECTED_SECRETS=arn)
Example #18
0
    def edit_config(self, information_fetcher=None):
        '''
            Open editor to update configuration
        '''

        try:
            current_configuration = self.get_config()

            updated_configuration = edit(
                text=json.dumps(
                    current_configuration,
                    indent=4,
                    sort_keys=True,
                    cls=DecimalEncoder
                ),
                extension=".json"
            )

            if updated_configuration is None:
                self.set_config(current_configuration, information_fetcher)
                if self.new_service:
                    log_warning("Using default configuration.")
                else:
                    log_warning("No changes made.")
            else:
                updated_configuration = json.loads(updated_configuration)
                differences = list(dictdiffer.diff(
                    current_configuration,
                    updated_configuration
                ))
                if not differences:
                    log_warning("No changes made.")
                    self.set_config(current_configuration, information_fetcher)
                else:
                    print_json_changes(differences)
                    if confirm('Do you want update the config?'):
                        self.set_config(updated_configuration)
                    else:
                        log_warning("Changes aborted.")
                        self.set_config(current_configuration, information_fetcher)
        except ClientError:
            raise UnrecoverableException("Unable to fetch service configuration from DynamoDB.")
Example #19
0
def edit_config(name, environment, sidecar_name, no_editor=False, values=None, confirm=False):
    if values is None:
        values = {}

    parameter_store = ParameterStore(name, environment)
    env_config_strings = parameter_store.get_existing_config_as_string(sidecar_name)

    if no_editor:
        differences = list()
        split_env_configs = list(map(lambda x: x.split('='), env_config_strings.split('\n')))
        for key, value in values.items():
            existing = next(d for d in split_env_configs if d[0] == key)
            if existing:
                differences.append(('change', key, (existing[1], value)))
            else:
                differences.append(('add', '', [(key, value)]))
        print_parameter_changes(differences)
        if confirm or click.confirm('Do you want update the config?'):
            parameter_store.set_config(differences, sidecar_name)
        else:
            log_warning("Changes aborted.")
    else:
        edited_config_content = click.edit(str(env_config_strings), extension=".properties")

        if edited_config_content is None:
            log_warning("No changes made, exiting.")
            return

        differences = list(dictdiffer.diff(
            read_config(env_config_strings),
            read_config(edited_config_content)
        ))
        if not differences:
            log_warning("No changes made, exiting.")
        else:
            print_parameter_changes(differences)
            if click.confirm('Do you want update the config?'):
                parameter_store.set_config(differences, sidecar_name)
            else:
                log_warning("Changes aborted.")
Example #20
0
def edit_config(name, environment):
    parameter_store = ParameterStore(name, environment)
    env_config_strings = parameter_store.get_existing_config_as_string()
    edited_config_content = click.edit(str(env_config_strings))

    if edited_config_content is None:
        log_warning("No changes made, exiting.")
        return

    differences = list(
        dictdiffer.diff(read_config(env_config_strings),
                        read_config(edited_config_content)))
    if not differences:
        log_warning("No changes made, exiting.")
    else:
        print_parameter_changes(differences)
        if click.confirm('Do you want update the config?'):
            parameter_store.set_config(differences)
        else:
            log_warning("Changes aborted.")
Example #21
0
    def _edit_config(self):
        '''
            Open editor to update configuration
        '''
        try:
            current_configuration = self.get_config()
            previous_cloudlift_version = current_configuration.pop('cloudlift_version', None)
            updated_configuration = edit(
                json.dumps(
                    current_configuration,
                    indent=4,
                    sort_keys=True,
                    cls=DecimalEncoder
                )
            )

            if updated_configuration is None:
                log_warning("No changes made.")
            else:
                updated_configuration = json.loads(updated_configuration)
                differences = list(dictdiffer.diff(
                    current_configuration,
                    updated_configuration
                ))
                if not differences:
                    log_warning("No changes made.")
                    updated_configuration['cloudlift_version']=VERSION
                    self._set_config(updated_configuration)
                    # self.update_cloudlift_version()
                else:
                    print_json_changes(differences)
                    if confirm('Do you want update the config?'):
                        self._set_config(updated_configuration)
                        # self.update_cloudlift_version()
                    else:
                        log_warning("Changes aborted.")
        except ClientError:
            raise UnrecoverableException("Unable to fetch environment configuration from DynamoDB.")
Example #22
0
 def _create_config(self):
     log_warning(
         "\nConfiguration for this environment was not found in DynamoDB.\
         \nInitiating prompts for setting up configuration.\
         \nIf this environment was previously configured, please ensure\
         \ndefault region is the same as previously used. Otherwise, use\
         \nthe same configuration.\n"
     )
     region = prompt("AWS region for environment", default='ap-south-1')
     vpc_cidr = ipaddress.IPv4Network(prompt("VPC CIDR", default='10.10.10.10/16'))
     nat_eip = prompt("Allocation ID Elastic IP for NAT")
     public_subnet_1_cidr = prompt(
         "Public Subnet 1 CIDR", default=list(vpc_cidr.subnets(new_prefix=22))[0])
     public_subnet_2_cidr = prompt(
         "Public Subnet 2 CIDR", default=list(vpc_cidr.subnets(new_prefix=22))[1])
     private_subnet_1_cidr = prompt(
         "Private Subnet 1 CIDR", default=list(vpc_cidr.subnets(new_prefix=22))[2])
     private_subnet_2_cidr = prompt(
         "Private Subnet 2 CIDR", default=list(vpc_cidr.subnets(new_prefix=22))[3])
     cluster_min_instances = prompt("Min instances in cluster", default=1)
     cluster_max_instances = prompt("Max instances in cluster", default=5)
     cluster_instance_type = prompt("Instance type", default='m5.xlarge')
     key_name = prompt("SSH key name")
     notifications_arn = prompt("Notification SNS ARN")
     ssl_certificate_arn = prompt("SSL certificate ARN")
     environment_configuration = {self.environment: {
         "region": region,
         "vpc": {
             "cidr": str(vpc_cidr),
             "nat-gateway": {
                 "elastic-ip-allocation-id": nat_eip
             },
             "subnets": {
                 "public": {
                     "subnet-1": {
                         "cidr": str(public_subnet_1_cidr)
                     },
                     "subnet-2": {
                         "cidr": str(public_subnet_2_cidr)
                     }
                 },
                 "private": {
                     "subnet-1": {
                         "cidr": str(private_subnet_1_cidr)
                     },
                     "subnet-2": {
                         "cidr": str(private_subnet_2_cidr)
                     }
                 }
             }
         },
         "cluster": {
             "min_instances": cluster_min_instances,
             "max_instances": cluster_max_instances,
             "instance_type": cluster_instance_type,
             "key_name": key_name
         },
         "environment": {
             "notifications_arn": notifications_arn,
             "ssl_certificate_arn": ssl_certificate_arn
         },
     }, 'cloudlift_version': VERSION}
     self._set_config(environment_configuration)
     pass