Skip to content

Commit

Permalink
Run ruff format
Browse files Browse the repository at this point in the history
  • Loading branch information
kdaily committed Jan 13, 2025
1 parent 320f80a commit 7e07719
Show file tree
Hide file tree
Showing 25 changed files with 1,283 additions and 786 deletions.
222 changes: 138 additions & 84 deletions awscli/customizations/cloudformation/artifact_exporter.py

Large diffs are not rendered by default.

201 changes: 107 additions & 94 deletions awscli/customizations/cloudformation/deploy.py

Large diffs are not rendered by default.

139 changes: 95 additions & 44 deletions awscli/customizations/cloudformation/deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,26 @@
import collections

from awscli.customizations.cloudformation import exceptions
from awscli.customizations.cloudformation.artifact_exporter import mktempfile, parse_s3_url
from awscli.customizations.cloudformation.artifact_exporter import (
mktempfile,
parse_s3_url,
)

from datetime import datetime

LOG = logging.getLogger(__name__)

ChangeSetResult = collections.namedtuple(
"ChangeSetResult", ["changeset_id", "changeset_type"])
"ChangeSetResult", ["changeset_id", "changeset_type"]
)


class Deployer(object):

def __init__(self, cloudformation_client,
changeset_prefix="awscli-cloudformation-package-deploy-"):
def __init__(
self,
cloudformation_client,
changeset_prefix="awscli-cloudformation-package-deploy-",
):
self._client = cloudformation_client
self.changeset_prefix = changeset_prefix

Expand Down Expand Up @@ -63,17 +69,26 @@ def has_stack(self, stack_name):
msg = str(e)

if "Stack with id {0} does not exist".format(stack_name) in msg:
LOG.debug("Stack with id {0} does not exist".format(
stack_name))
LOG.debug(
"Stack with id {0} does not exist".format(stack_name)
)
return False
else:
# We don't know anything about this exception. Don't handle
LOG.debug("Unable to get stack details.", exc_info=e)
raise e

def create_changeset(self, stack_name, cfn_template,
parameter_values, capabilities, role_arn,
notification_arns, s3_uploader, tags):
def create_changeset(
self,
stack_name,
cfn_template,
parameter_values,
capabilities,
role_arn,
notification_arns,
s3_uploader,
tags,
):
"""
Call Cloudformation to create a changeset and wait for it to complete
Expand All @@ -96,17 +111,27 @@ def create_changeset(self, stack_name, cfn_template,
# When creating a new stack, UsePreviousValue=True is invalid.
# For such parameters, users should either override with new value,
# or set a Default value in template to successfully create a stack.
parameter_values = [x for x in parameter_values
if not x.get("UsePreviousValue", False)]
parameter_values = [
x
for x in parameter_values
if not x.get("UsePreviousValue", False)
]
else:
changeset_type = "UPDATE"
# UsePreviousValue not valid if parameter is new
summary = self._client.get_template_summary(StackName=stack_name)
existing_parameters = [parameter['ParameterKey'] for parameter in \
summary['Parameters']]
parameter_values = [x for x in parameter_values
if not (x.get("UsePreviousValue", False) and \
x["ParameterKey"] not in existing_parameters)]
existing_parameters = [
parameter['ParameterKey']
for parameter in summary['Parameters']
]
parameter_values = [
x
for x in parameter_values
if not (
x.get("UsePreviousValue", False)
and x["ParameterKey"] not in existing_parameters
)
]

kwargs = {
'ChangeSetName': changeset_name,
Expand All @@ -126,10 +151,13 @@ def create_changeset(self, stack_name, cfn_template,
temporary_file.write(kwargs.pop('TemplateBody'))
temporary_file.flush()
url = s3_uploader.upload_with_dedup(
temporary_file.name, "template")
temporary_file.name, "template"
)
# TemplateUrl property requires S3 URL to be in path-style format
parts = parse_s3_url(url, version_property="Version")
kwargs['TemplateURL'] = s3_uploader.to_path_style_s3_url(parts["Key"], parts.get("Version", None))
kwargs['TemplateURL'] = s3_uploader.to_path_style_s3_url(
parts["Key"], parts.get("Version", None)
)

# don't set these arguments if not specified to use existing values
if role_arn is not None:
Expand Down Expand Up @@ -159,26 +187,34 @@ def wait_for_changeset(self, changeset_id, stack_name):
# Poll every 5 seconds. Changeset creation should be fast
waiter_config = {'Delay': 5}
try:
waiter.wait(ChangeSetName=changeset_id, StackName=stack_name,
WaiterConfig=waiter_config)
waiter.wait(
ChangeSetName=changeset_id,
StackName=stack_name,
WaiterConfig=waiter_config,
)
except botocore.exceptions.WaiterError as ex:
LOG.debug("Create changeset waiter exception", exc_info=ex)

resp = ex.last_response
status = resp["Status"]
reason = resp["StatusReason"]

if status == "FAILED" and \
"The submitted information didn't contain changes." in reason or \
"No updates are to be performed" in reason:
raise exceptions.ChangeEmptyError(stack_name=stack_name)

raise RuntimeError("Failed to create the changeset: {0} "
"Status: {1}. Reason: {2}"
.format(ex, status, reason))

def execute_changeset(self, changeset_id, stack_name,
disable_rollback=False):
if (
status == "FAILED"
and "The submitted information didn't contain changes."
in reason
or "No updates are to be performed" in reason
):
raise exceptions.ChangeEmptyError(stack_name=stack_name)

raise RuntimeError(
"Failed to create the changeset: {0} "
"Status: {1}. Reason: {2}".format(ex, status, reason)
)

def execute_changeset(
self, changeset_id, stack_name, disable_rollback=False
):
"""
Calls CloudFormation to execute changeset
Expand All @@ -188,12 +224,12 @@ def execute_changeset(self, changeset_id, stack_name,
:return: Response from execute-change-set call
"""
return self._client.execute_change_set(
ChangeSetName=changeset_id,
StackName=stack_name,
DisableRollback=disable_rollback)
ChangeSetName=changeset_id,
StackName=stack_name,
DisableRollback=disable_rollback,
)

def wait_for_execute(self, stack_name, changeset_type):

sys.stdout.write("Waiting for stack create/update to complete\n")
sys.stdout.flush()

Expand All @@ -203,8 +239,9 @@ def wait_for_execute(self, stack_name, changeset_type):
elif changeset_type == "UPDATE":
waiter = self._client.get_waiter("stack_update_complete")
else:
raise RuntimeError("Invalid changeset type {0}"
.format(changeset_type))
raise RuntimeError(
"Invalid changeset type {0}".format(changeset_type)
)

# Poll every 30 seconds. Polling too frequently risks hitting rate limits
# on CloudFormation's DescribeStacks API
Expand All @@ -220,13 +257,27 @@ def wait_for_execute(self, stack_name, changeset_type):

raise exceptions.DeployFailedError(stack_name=stack_name)

def create_and_wait_for_changeset(self, stack_name, cfn_template,
parameter_values, capabilities, role_arn,
notification_arns, s3_uploader, tags):

def create_and_wait_for_changeset(
self,
stack_name,
cfn_template,
parameter_values,
capabilities,
role_arn,
notification_arns,
s3_uploader,
tags,
):
result = self.create_changeset(
stack_name, cfn_template, parameter_values, capabilities,
role_arn, notification_arns, s3_uploader, tags)
stack_name,
cfn_template,
parameter_values,
capabilities,
role_arn,
notification_arns,
s3_uploader,
tags,
)
self.wait_for_changeset(result.changeset_id, stack_name)

return result
53 changes: 30 additions & 23 deletions awscli/customizations/cloudformation/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

class CloudFormationCommandError(Exception):
fmt = 'An unspecified error occurred'

Expand All @@ -17,42 +16,50 @@ class ChangeEmptyError(CloudFormationCommandError):


class InvalidLocalPathError(CloudFormationCommandError):
fmt = ("Parameter {property_name} of resource {resource_id} refers "
"to a file or folder that does not exist {local_path}")
fmt = (
"Parameter {property_name} of resource {resource_id} refers "
"to a file or folder that does not exist {local_path}"
)


class InvalidTemplateUrlParameterError(CloudFormationCommandError):
fmt = ("{property_name} parameter of {resource_id} resource is invalid. "
"It must be a S3 URL or path to CloudFormation "
"template file. Actual: {template_path}")
fmt = (
"{property_name} parameter of {resource_id} resource is invalid. "
"It must be a S3 URL or path to CloudFormation "
"template file. Actual: {template_path}"
)


class ExportFailedError(CloudFormationCommandError):
fmt = ("Unable to upload artifact {property_value} referenced "
"by {property_name} parameter of {resource_id} resource."
"\n"
"{ex}")
fmt = (
"Unable to upload artifact {property_value} referenced "
"by {property_name} parameter of {resource_id} resource."
"\n"
"{ex}"
)


class InvalidKeyValuePairArgumentError(CloudFormationCommandError):
fmt = ("{value} value passed to --{argname} must be of format "
"Key=Value")
fmt = "{value} value passed to --{argname} must be of format " "Key=Value"


class DeployFailedError(CloudFormationCommandError):
fmt = \
("Failed to create/update the stack. Run the following command"
"\n"
"to fetch the list of events leading up to the failure"
"\n"
"aws cloudformation describe-stack-events --stack-name {stack_name}")
fmt = (
"Failed to create/update the stack. Run the following command"
"\n"
"to fetch the list of events leading up to the failure"
"\n"
"aws cloudformation describe-stack-events --stack-name {stack_name}"
)


class DeployBucketRequiredError(CloudFormationCommandError):
fmt = \
("Templates with a size greater than 51,200 bytes must be deployed "
"via an S3 Bucket. Please add the --s3-bucket parameter to your "
"command. The local template will be copied to that S3 bucket and "
"then deployed.")
fmt = (
"Templates with a size greater than 51,200 bytes must be deployed "
"via an S3 Bucket. Please add the --s3-bucket parameter to your "
"command. The local template will be copied to that S3 bucket and "
"then deployed."
)


class InvalidForEachIntrinsicFunctionError(CloudFormationCommandError):
Expand Down
Loading

0 comments on commit 7e07719

Please sign in to comment.