Exemplo n.º 1
0
def update_stack(context, args):

    deployment_name = args.deployment
    resource_group_name = args.resource_group

    # Use default deployment if necessary
    if deployment_name is None:
        if context.config.default_deployment is None:
            raise HandledError('No default deployment has been set. Provide the --deployment parameter or use the default-deployment command to set a default deployment.')
        deployment_name = context.config.default_deployment

    # Get needed data, verifies the resource group stack exists

    resource_group = context.resource_groups.get(resource_group_name)
    resource_group_stack_id = resource_group.get_stack_id(deployment_name)
    pending_resource_status = resource_group.get_pending_resource_status(deployment_name)

    # Is it ok to do this?

    capabilities = context.stack.confirm_stack_operation(
        resource_group_stack_id,
        'deployment {} resource group {}'.format(deployment_name, resource_group_name),
        args,
        pending_resource_status
    )

    # Update the stack...

    project_uploader = ProjectUploader(context)
    deployment_uploader = project_uploader.get_deployment_uploader(deployment_name)

    resource_group_uploader, resource_group_template_url = before_update(
        deployment_uploader, 
        resource_group_name
    )

    parameters = resource_group.get_stack_parameters(
        deployment_name, 
        uploader = resource_group_uploader
    )

    # wait a bit for S3 to help insure that templates can be read by cloud formation
    time.sleep(constant.STACK_UPDATE_DELAY_TIME)

    context.stack.update(
        resource_group_stack_id, 
        resource_group_template_url, 
        parameters = parameters, 
        pending_resource_status = pending_resource_status,
        capabilities = capabilities
    )

    after_update(deployment_uploader, resource_group_name)

    # Deprecated in 1.9 - TODO remove
    context.hooks.call_module_handlers('cli-plugin-code/resource_group_hooks.py', 'on_post_update', 
        args=[deployment_name, resource_group_name], 
        deprecated=True
    )
Exemplo n.º 2
0
def _update_access_stack(context, args, deployment_name):

    # Get the data we need...

    deployment_stack_id = context.config.get_deployment_stack_id(
        deployment_name)
    deployment_access_stack_id = context.config.get_deployment_access_stack_id(
        deployment_name)
    pending_resource_status = __get_pending_access_resource_status(
        context, deployment_name)

    # Is it ok to do this?

    capabilities = context.stack.confirm_stack_operation(
        deployment_access_stack_id,
        'deployment {} access'.format(deployment_name), args,
        pending_resource_status)

    # Do the update...

    project_uploader = ProjectUploader(context)
    deployment_uploader = project_uploader.get_deployment_uploader(
        deployment_name)

    context.view.processing_template('{} deployment'.format(deployment_name))

    access_template_url = deployment_uploader.upload_content(
        constant.DEPLOYMENT_ACCESS_TEMPLATE_FILENAME,
        json.dumps(context.config.deployment_access_template_aggregator.
                   effective_template,
                   indent=4,
                   sort_keys=True), 'Configured Deployment Access Template')

    parameters = __get_access_stack_parameters(
        context,
        deployment_name,
        deployment_stack_id=deployment_stack_id,
        uploader=deployment_uploader)

    # wait a bit for S3 to help insure that templates can be read by cloud formation
    time.sleep(constant.STACK_UPDATE_DELAY_TIME)

    context.stack.update(deployment_access_stack_id,
                         access_template_url,
                         parameters,
                         pending_resource_status=pending_resource_status,
                         capabilities=capabilities)
Exemplo n.º 3
0
def __update_project_stack(context, pending_resource_status, capabilities,
                           args):
    # Upload the project template and code directory.

    project_uploader = ProjectUploader(context)

    context.view.processing_template('project')

    project_template_url = project_uploader.upload_content(
        constant.PROJECT_TEMPLATE_FILENAME,
        json.dumps(
            context.config.project_template_aggregator.effective_template,
            indent=4,
            sort_keys=True), 'processed project template')

    __zip_individual_lambda_code_folders(context, project_uploader)

    # Deprecated in 1.9. TODO: remove
    # Execute all the uploader pre hooks before the resources are updated
    project_uploader.execute_uploader_pre_hooks()

    kwargs = {'project_uploader': project_uploader, 'args': args}
    context.hooks.call_module_handlers('resource-manager-code/update.py',
                                       'before_project_updated',
                                       kwargs=kwargs)

    # wait a bit for S3 to help insure that templates can be read by cloud formation
    time.sleep(constant.STACK_UPDATE_DELAY_TIME)

    # Update the stack
    parameters = __get_parameters(context, project_uploader)

    context.stack.update(context.config.project_stack_id,
                         project_template_url,
                         parameters=parameters,
                         pending_resource_status=pending_resource_status,
                         capabilities=capabilities)

    # wait a bit for S3 to help insure that templates can be read by cloud formation
    time.sleep(constant.STACK_UPDATE_DELAY_TIME)

    # Project is fully initialized only after the first successful update
    # Project resource intialization is based on having a ProjectStackId in your local-project-settings.json
    # Post hooks could be dependant on the availability of project_resources
    # So we save the pending stack id which is used during Project stack creation
    # Then we reinitialize the config.__project_resources to make the project_resources available to hooks even during a project stack creation
    context.config.save_pending_project_stack_id()

    # Deprecated in 1.9. TODO: remove
    # Now all the stack resources should be available to the hooks
    project_uploader.execute_uploader_post_hooks()

    context.hooks.call_module_handlers('resource-manager-code/update.py',
                                       'after_project_updated',
                                       kwargs=kwargs)
Exemplo n.º 4
0
def __get_pending_resource_status(context, deleting=False):

    stack_id = context.config.project_stack_id
    if not stack_id:
        stack_id = context.config.get_pending_project_stack_id()

    if deleting:
        template = {}
        parameters = {}
    else:
        template = context.config.project_template_aggregator.effective_template
        parameters = __get_parameters(context, uploader=None)

    lambda_function_content_paths = []

    resources = context.config.project_template_aggregator.effective_template.get(
        "Resources", {})
    for name, description in resources.iteritems():
        if not description["Type"] == "AWS::Lambda::Function":
            continue

        function_runtime = lambda_utils.get_function_runtime(
            name, description, resources)

        code_path, imported_paths, multi_imports = ProjectUploader.get_lambda_function_code_paths(
            context, name, function_runtime)

        lambda_function_content_paths.append(code_path)
        lambda_function_content_paths.extend(imported_paths)

        if name == 'ProjectResourceHandler':
            lambda_function_content_paths.extend(
                __get_plugin_project_code_paths(context).values())

    # TODO: need to support swagger.json IN the lambda directory.
    service_api_content_paths = [
        os.path.join(context.config.framework_aws_directory_path,
                     'swagger.json')
    ]

    # TODO: get_pending_resource_status's new_content_paths parameter needs to support
    # a per-resource mapping instead of an per-type mapping. As is, a change in any lambda
    # directory makes all lambdas look like they need to be updated.
    return context.stack.get_pending_resource_status(
        stack_id,
        new_template=template,
        new_parameter_values=parameters,
        new_content_paths={
            'AWS::Lambda::Function': lambda_function_content_paths,
            'Custom::ServiceApi': service_api_content_paths
        })
Exemplo n.º 5
0
def update_stack(context, args):

    # Use default deployment if necessary

    if args.deployment is None:
        if context.config.default_deployment is None:
            raise HandledError('No default deployment has been set. Provide the --deployment parameter or use the default-deployment command to set a default deployment.')
        args.deployment = context.config.default_deployment

    # Does deployment-template.json include resource group from a gem which isn't enabled for the project?
    for resource_group_name in context.resource_groups.keys():
         __check_resource_group_gem_status(context, resource_group_name)

    # Resource group (and other) file write checks
    create_and_validate_writable_list(context)

    # Get necessary data, verifies project has been initialized and that the stack exists.

    deployment_stack_id = context.config.get_deployment_stack_id(args.deployment)
    pending_resource_status = __get_pending_deployment_resource_status(context, args.deployment)

    # Is it ok to do this?

    capabilities = context.stack.confirm_stack_operation(
        deployment_stack_id,
        'deployment {}'.format(args.deployment),
        args,
        pending_resource_status,
        ignore_resource_types = [ 'Custom::EmptyDeployment' ]
    )

    # Do the upload ...

    project_uploader = ProjectUploader(context)
    deployment_uploader = project_uploader.get_deployment_uploader(args.deployment)

    deployment_template_url = before_update(context, deployment_uploader)

    parameters = __get_deployment_stack_parameters(context, args.deployment, uploader = deployment_uploader)

    # wait a bit for S3 to help insure that templates can be read by cloud formation
    time.sleep(constant.STACK_UPDATE_DELAY_TIME)

    context.stack.update(
        deployment_stack_id, 
        deployment_template_url, 
        parameters, 
        pending_resource_status = pending_resource_status,
        capabilities = capabilities
    )

    after_update(context, deployment_uploader)  

    # Update mappings...

    if args.deployment == context.config.default_deployment:
        mappings.update(context, util.Args())

    if args.deployment == context.config.release_deployment:
        temp_args = util.Args()
        temp_args.release = True
        mappings.update(context, temp_args)
Exemplo n.º 6
0
def create_stack(context, args):

    # Has the project been initialized?
    if not context.config.project_initialized:
        raise HandledError('The project has not been initialized.')

    # Does a deployment with that name already exist?
    if context.config.deployment_stack_exists(args.deployment):
        raise HandledError('The project already has a {} deployment.'.format(args.deployment))

    # Does deployment-template.json include resource group from a gem which isn't enabled for the project?
    for resource_group_name in context.resource_groups.keys():
         __check_resource_group_gem_status(context, resource_group_name)

    # Is the project settings file writable?
    context.config.validate_writable(context.config.local_project_settings_path)

    # Is the deployment name valid?
    util.validate_stack_name(args.deployment)

    # If there is no project default deployment, make this the project default deployment
    if context.config.project_default_deployment is None:
        args.make_project_default = True

    # If there is no release deployment, make this the release deployment
    if context.config.release_deployment is None:
        args.make_release_deployment = True

    # Need to handle situations where the deployment and/or access stack were
    # not successfully created on previous attempts.

    pending_deployment_stack_id = context.config.get_pending_deployment_stack_id(args.deployment)
    pending_deployment_access_stack_id = context.config.get_pending_deployment_access_stack_id(args.deployment)

    pending_deployment_stack_status = context.stack.get_stack_status(pending_deployment_stack_id)
    pending_deployment_access_stack_status = context.stack.get_stack_status(pending_deployment_access_stack_id)

    # Does a stack with the name already exist? It's ok if a previous attempt
    # at creation left a stack with this name behind, we'll deal with that later.
    deployment_stack_name = args.stack_name or context.config.get_default_deployment_stack_name(args.deployment)
    deployment_region = util.get_region_from_arn(context.config.project_stack_id)
    if pending_deployment_stack_id is None or deployment_stack_name != util.get_stack_name_from_arn(pending_deployment_stack_id):
        if context.stack.name_exists(deployment_stack_name, deployment_region):
            raise HandledError('An AWS Cloud Formation stack with the name {} already exists in region {}. Use the --stack-name option to provide a different name.'.format(deployment_stack_name, deployment_region))

    # Resource group (and other) file write checks
    create_and_validate_writable_list(context)

    # Is it ok to use AWS?

    pending_resource_status = __get_pending_combined_resource_status(context, args.deployment)

    capabilities = context.stack.confirm_stack_operation(
        None, # stack id
        'deployment {}'.format(args.deployment),
        args,
        pending_resource_status,
        ignore_resource_types = [ 'Custom::EmptyDeployment' ]
    )

    # We have the following scenerios to deal with:
    #
    # 1) This is the first attempt to create the deployment, or previous attempts didn't
    #    get as far as creating any stacks.
    #
    # 2) The previous attempt failed to create or update the deployment stack, which was
    # left in a ROLLBACK_COMPLETED, UPDATE_ROLLBACK_FAILED, or ROLLBACK_FAILED state. This
    # stack must be deleted and a new one created.
    #
    # 3) The previous attempt created the deployment stack but failed to create the access
    # stack, leaving it in the ROLLBACK_COMPLETED state. In this case we update the deployment
    # stack (to make sure it reflects any changes that may have been made), delete the access
    # stack and attempt to create a new one.
    #
    # 4) Both the deployment and access stacks were created successfully, but the pending
    # stack id properites in the config were not replaced with the non-pending properties
    # (this could happen if someone kills the client during the access stack creation
    # process, which then runs to a successful completion). In this case we update both
    # stacks to make sure they reflect any changes, then replace the "pending" stack id
    # properties.

    project_uploader = ProjectUploader(context)
    deployment_uploader = project_uploader.get_deployment_uploader(args.deployment)

    template_url = before_update(context, deployment_uploader)

    deployment_stack_parameters = __get_deployment_stack_parameters(context, args.deployment, uploader = deployment_uploader)

    # wait a bit for S3 to help insure that templates can be read by cloud formation
    time.sleep(constant.STACK_UPDATE_DELAY_TIME)

    try:

        if pending_deployment_stack_status not in [None, context.stack.STATUS_ROLLBACK_COMPLETE, context.stack.STATUS_DELETE_COMPLETE, context.stack.STATUS_UPDATE_ROLLBACK_FAILED, context.stack.STATUS_ROLLBACK_FAILED]:

            # case 3 or 4 - deployment stack was previously created successfully, update it

            context.stack.update(
                pending_deployment_stack_id, 
                template_url, 
                deployment_stack_parameters,
                capabilities = capabilities
            )
            deployment_stack_id = pending_deployment_stack_id

        else:

            if pending_deployment_stack_status in [context.stack.STATUS_ROLLBACK_COMPLETE, context.stack.STATUS_ROLLBACK_FAILED, context.stack.STATUS_UPDATE_ROLLBACK_FAILED]:

                # case 2 - deployment stack failed to create previously, delete it

                context.stack.delete(pending_deployment_stack_id)

            # case 1 and 2 - deployment stack wasn't creatred previously or was just
            # deleted, attempt to create it

            deployment_stack_id = context.stack.create_using_url(
                deployment_stack_name,
                template_url,
                deployment_region,
                deployment_stack_parameters,
                created_callback=lambda id: context.config.set_pending_deployment_stack_id(args.deployment, id),
                capabilities = capabilities)

        # Now create or update the access stack...

        context.view.processing_template('{} deployment'.format(args.deployment))

        access_template_url = deployment_uploader.upload_content(
            constant.DEPLOYMENT_ACCESS_TEMPLATE_FILENAME, 
            json.dumps(context.config.deployment_access_template_aggregator.effective_template, indent=4, sort_keys=True),
            'processed deployment access temmplate')

        access_stack_parameters = __get_access_stack_parameters(
            context, 
            args.deployment, 
            deployment_stack_id = deployment_stack_id, 
            uploader = deployment_uploader
        )
        
        if pending_deployment_access_stack_status not in [None, context.stack.STATUS_ROLLBACK_COMPLETE, context.stack.STATUS_DELETE_COMPLETE]:

            # case 4 - access stack was previously created successfully but the pending
            # stack id properties were not replaced. Update the stack.

            context.stack.update(
                pending_deployment_access_stack_id, 
                access_template_url, 
                deployment_stack_parameters,
                capabilities = capabilities
            )

            deployment_access_stack_id = pending_deployment_access_stack_id

        else:

            if pending_deployment_access_stack_status == context.stack.STATUS_ROLLBACK_COMPLETE:

                # case 3 - access stack failed to create previously, delete it

                context.stack.delete(pending_deployment_access_stack_id)

            # case 1 or 3 - access stack wasn't created before, or was just deleted. Attempt
            # to create.

            deployment_access_stack_name = deployment_stack_name + '-Access'

            deployment_access_stack_id = context.stack.create_using_url(
                deployment_access_stack_name,
                access_template_url,
                deployment_region,
                parameters = access_stack_parameters,
                created_callback=lambda id: context.config.set_pending_deployment_access_stack_id(args.deployment, id),
                capabilities = capabilities)

    except:
        context.config.force_gui_refresh()
        raise

    context.config.force_gui_refresh()

    context.config.finalize_deployment_stack_ids(args.deployment)

    context.view.deployment_stack_created(args.deployment, deployment_stack_id, deployment_access_stack_id)

    # Should the new deployment become the project default deployment or the release deployment?

    if args.make_project_default:
        context.config.set_project_default_deployment(args.deployment)
        mappings.update(context, util.Args())
        context.view.default_deployment(context.config.user_default_deployment, context.config.project_default_deployment)

    if args.make_release_deployment:
        context.config.set_release_deployment(args.deployment)
        temp_args = util.Args()
        temp_args.release = True
        mappings.update(context, temp_args)
        context.view.release_deployment(context.config.release_deployment)
    
    after_update(context, deployment_uploader)
Exemplo n.º 7
0
def delete_stack(context, args):

    resource_group_stack_id = context.config.get_resource_group_stack_id(args.deployment, args.resource_group)
    pending_resource_status = context.stack.get_pending_resource_status(
        resource_group_stack_id, 
        new_template = {}
    )

    # Is it ok to do this?

    capabilities = context.stack.confirm_stack_operation(
        None, # stack id
        'deployment {} resource group {}'.format(args.deployment, args.resource_group),
        args,
        pending_resource_status
    )

    # Does a "safe" delete of a resource group stack. The existing deployment
    # template is modified to remove the stack and config resources and used
    # to update the deployment. This prevents unexpected changes to other resource
    # groups as a side effect of the deployment update.

    project_uploader = ProjectUploader(context)
    deployment_uploader = project_uploader.get_deployment_uploader(args.deployment)

    context.view.processing_template('{} deployment'.format(args.deployment))

    deployment_stack_id = context.config.get_deployment_stack_id(args.deployment)
    deployment_template = context.stack.get_current_template(deployment_stack_id)
    deployment_parameters = context.stack.get_current_parameters(deployment_stack_id)

    deployment_resources = deployment_template.get('Resources', {})

    resource_group_stack_resource = deployment_resources.get(args.resource_group, None)
    if resource_group_stack_resource is not None:
        del deployment_resources[args.resource_group]

    resource_group_config_resource = deployment_resources.get(args.resource_group + 'Configuration', None)
    if resource_group_config_resource is not None:
        del deployment_resources[args.resource_group + 'Configuration']

    if resource_group_stack_resource is None and resource_group_config_resource is None:
        raise HandledError('Definitions for {} resource group related resources where not found in the current {} deployment template.'.format(args.resource_group, args.deployment))

    if not deployment_resources:
        deployment_resources['EmptyDeployment'] = {
            "Type": "Custom::EmptyDeployment",
            "Properties": {
                "ServiceToken": { "Ref": "ProjectResourceHandler" }
            }
        }

    deployment_template_url = deployment_uploader.upload_content(constant.DEPLOYMENT_TEMPLATE_FILENAME, json.dumps(deployment_template),
                                                                 'deployment template without resource group definitions')

    resource_group_stack_id = context.stack.get_physical_resource_id(deployment_stack_id, args.resource_group)

    # wait a bit for S3 to help insure that templates can be read by cloud formation
    time.sleep(constant.STACK_UPDATE_DELAY_TIME)

    # Tell stack.update that a child stack is being deleted so that it
    # cleans up any resources that stack contains.
    pending_resource_status = {
        args.resource_group: {
            'OldDefinition': {
                'Type': 'AWS::CloudFormation::Stack'
            },
            'PendingAction': context.stack.PENDING_DELETE
        }
    }

    try:
        context.stack.update(
            deployment_stack_id, 
            deployment_template_url, 
            deployment_parameters, 
            pending_resource_status = pending_resource_status,
            capabilities = capabilities
        )
    except:
        context.config.force_gui_refresh()
        raise

    context.config.force_gui_refresh()

    context.view.resource_group_stack_deleted(args.deployment, args.resource_group)
Exemplo n.º 8
0
def create_stack(context, args):

    # Does a "safe" create of a resource group stack. The existing deployment
    # template is modified to add the stack and config resources and used
    # to update the deployment stack. This prevents unexpected changes to other
    # resource groups as a side effect of the deployment update.

    resource_group = context.resource_groups.get(args.resource_group)
    pending_resource_status = resource_group.get_pending_resource_status(args.deployment)

    # Is it ok to do this?

    capabilities = context.stack.confirm_stack_operation(
        None, # stack id
        'deployment {} resource group {}'.format(args.deployment, args.resource_group),
        args,
        pending_resource_status
    )

    # Do the create...

    project_uploader = ProjectUploader(context)
    deployment_uploader = project_uploader.get_deployment_uploader(args.deployment)

    before_update(
        deployment_uploader, 
        args.resource_group
    )

    context.view.processing_template('{} deployment'.format(args.deployment))

    deployment_stack_id = context.config.get_deployment_stack_id(args.deployment)
    deployment_template = context.stack.get_current_template(deployment_stack_id)
    deployment_parameters = context.stack.get_current_parameters(deployment_stack_id)

    deployment_resources = deployment_template.get('Resources', {})

    effective_deployment_resources = context.config.deployment_template_aggregator.effective_template.get('Resources',{})

    resource_group_stack_resource = deployment_resources.get(args.resource_group, None)
    if resource_group_stack_resource is None:
        resource_group_stack_resource = copy.deepcopy(effective_deployment_resources.get(args.resource_group, {}))
        deployment_resources[args.resource_group] = resource_group_stack_resource

    resource_group_config_name = args.resource_group + 'Configuration'
    resource_group_config_resource = deployment_resources.get(resource_group_config_name, None)
    if resource_group_config_resource is None:
        resource_group_config_resource = copy.deepcopy(effective_deployment_resources.get(resource_group_config_name, {}))
        resource_group_config_resource.get('Properties', {})['ConfigurationKey'] = deployment_uploader.key
        deployment_resources[resource_group_config_name] = resource_group_config_resource

    if 'EmptyDeployment' in deployment_resources:
        del deployment_resources['EmptyDeployment']

    deployment_template_url = deployment_uploader.upload_content(constant.DEPLOYMENT_TEMPLATE_FILENAME, json.dumps(deployment_template),
                                                                 'deployment template with resource group definitions')

    # wait a bit for S3 to help insure that templates can be read by cloud formation
    time.sleep(constant.STACK_UPDATE_DELAY_TIME)

    try:
        context.stack.update(
            deployment_stack_id, 
            deployment_template_url, 
            deployment_parameters,
            pending_resource_status = __nest_pending_resource_status(args.deployment, pending_resource_status),
            capabilities = capabilities
        )
    except:
        context.config.force_gui_refresh()
        raise

    context.config.force_gui_refresh()

    context.view.resource_group_stack_created(args.deployment, args.resource_group)

    after_update(deployment_uploader, args.resource_group)

    # Deprecated in 1.9 - TODO remove
    context.hooks.call_module_handlers('cli-plugin-code/resource_group_hooks.py', 'on_post_update', 
        args=[args.deployment, args.resource_group], 
        deprecated=True
    )