def run(self): """ run is the method called by Sceptre. It should carry out the work intended by this hook. self.argument is available from the base class and contains the argument defined in the sceptre config file (see below) The following attributes may be available from the base class: self.stack_config (A dict of data from <stack_name>.yaml) self.environment_config (A dict of data from config.yaml) self.connection_manager (A connection_manager) """ environment = self.environment_config.environment_path + "/" + self.stack_config.name stack = Stack(name=environment, environment_config=self.environment_config, connection_manager=self.connection_manager) try: outputs = stack.describe_outputs() except: return if outputs: client_artifacts_s3_bucket_name = [output['OutputValue'] for output in outputs if output['OutputKey'] == 'EnvironmentArtifactsS3Bucket'] print(client_artifacts_s3_bucket_name[0]) s3 = boto3.resource('s3') for bucket in s3.buckets.all(): print(bucket.name) if bucket.name == client_artifacts_s3_bucket_name[0]: bucket.object_versions.delete()
def setup_method(self, test_method): self.stack = Stack( name='dev/app/stack', project_code=sentinel.project_code, template_bucket_name=sentinel.template_bucket_name, template_key_prefix=sentinel.template_key_prefix, required_version=sentinel.required_version, template_path=sentinel.template_path, region=sentinel.region, profile=sentinel.profile, parameters={"key1": "val1"}, sceptre_user_data=sentinel.sceptre_user_data, hooks={}, s3_details=None, dependencies=sentinel.dependencies, role_arn=sentinel.role_arn, protected=False, tags={"tag1": "val1"}, external_name=sentinel.external_name, notifications=[sentinel.notification], on_failure=sentinel.on_failure, iam_role=sentinel.iam_role, iam_role_session_duration=sentinel.iam_role_session_duration, stack_timeout=sentinel.stack_timeout, stack_group_config={}) self.stack._template = MagicMock(spec=Template)
def test_raises_exception_if_path_and_handler_configured(self): with pytest.raises(InvalidConfigFileError): Stack(name="stack_name", project_code="project_code", template_path="template_path", template_handler_config={"type": "file"}, region="region")
def test_initiate_stack_with_template_path(self): stack = Stack(name='dev/stack/app', project_code=sentinel.project_code, template_path=sentinel.template_path, template_bucket_name=sentinel.template_bucket_name, template_key_prefix=sentinel.template_key_prefix, required_version=sentinel.required_version, region=sentinel.region, external_name=sentinel.external_name) assert stack.name == 'dev/stack/app' assert stack.project_code == sentinel.project_code assert stack.template_bucket_name == sentinel.template_bucket_name assert stack.template_key_prefix == sentinel.template_key_prefix assert stack.required_version == sentinel.required_version assert stack.external_name == sentinel.external_name assert stack.hooks == {} assert stack.parameters == {} assert stack.sceptre_user_data == {} assert stack.template_path == sentinel.template_path assert stack.template_handler_config is None assert stack.s3_details is None assert stack._template is None assert stack.protected is False assert stack.iam_role is None assert stack.role_arn is None assert stack.dependencies == [] assert stack.tags == {} assert stack.notifications == [] assert stack.on_failure is None assert stack.stack_group_config == {}
def setup_method(self, test_method): self.patcher_SceptrePlan = patch("sceptre.plan.plan.SceptrePlan") self.stack = Stack(name='dev/app/stack', project_code=sentinel.project_code, template_path=sentinel.template_path, region=sentinel.region, profile=sentinel.profile, parameters={"key1": "val1"}, sceptre_user_data=sentinel.sceptre_user_data, hooks={}, s3_details=None, dependencies=sentinel.dependencies, role_arn=sentinel.role_arn, protected=False, tags={"tag1": "val1"}, external_name=sentinel.external_name, notifications=[sentinel.notification], on_failure=sentinel.on_failure, stack_timeout=sentinel.stack_timeout) self.mock_context = MagicMock(spec=SceptreContext) self.mock_config_reader = MagicMock(spec=ConfigReader) self.mock_context.project_path = sentinel.project_path self.mock_context.command_path = sentinel.command_path self.mock_context.config_file = sentinel.config_file self.mock_context.full_config_path.return_value =\ sentinel.full_config_path self.mock_context.user_variables = {} self.mock_context.options = {} self.mock_context.no_colour = True self.mock_config_reader.context = self.mock_context
def setup_method(self, test_method): self.patcher_connection_manager = patch( "sceptre.plan.actions.ConnectionManager") self.mock_ConnectionManager = self.patcher_connection_manager.start() self.stack = Stack(name='prod/app/stack', project_code=sentinel.project_code, template_path=sentinel.template_path, region=sentinel.region, profile=sentinel.profile, parameters={"key1": "val1"}, sceptre_user_data=sentinel.sceptre_user_data, hooks={}, s3_details=None, dependencies=sentinel.dependencies, role_arn=sentinel.role_arn, protected=False, tags={"tag1": "val1"}, external_name=sentinel.external_name, notifications=[sentinel.notification], on_failure=sentinel.on_failure, stack_timeout=sentinel.stack_timeout) self.actions = StackActions(self.stack) self.template = Template("fixtures/templates", self.stack.sceptre_user_data, self.actions.connection_manager, self.stack.s3_details) self.stack._template = self.template
def stack_factory(**kwargs): call_kwargs = { 'name': 'dev/app/stack', 'project_code': sentinel.project_code, 'template_bucket_name': sentinel.template_bucket_name, 'template_key_prefix': sentinel.template_key_prefix, 'required_version': sentinel.required_version, 'template_path': sentinel.template_path, 'region': sentinel.region, 'profile': sentinel.profile, 'parameters': { "key1": "val1" }, 'sceptre_user_data': sentinel.sceptre_user_data, 'hooks': {}, 's3_details': None, 'dependencies': sentinel.dependencies, 'role_arn': sentinel.role_arn, 'protected': False, 'tags': { "tag1": "val1" }, 'external_name': sentinel.external_name, 'notifications': [sentinel.notification], 'on_failure': sentinel.on_failure, 'stack_timeout': sentinel.stack_timeout, 'stack_group_config': {} } call_kwargs.update(kwargs) return Stack(**call_kwargs)
def _construct_stack(self, rel_path, stack_group_config=None): """ Constructs an individual Stack object from a config path and a base config. :param rel_path: A relative config file path. :type rel_path: str :param stack_group_config: The Stack group config to use as defaults. :type stack_group_config: dict :returns: Stack object :rtype: sceptre.stack.Stack """ directory, filename = path.split(rel_path) if filename == self.context.config_file: pass self.templating_vars["stack_group_config"] = stack_group_config parsed_stack_group_config = self._parsed_stack_group_config( stack_group_config) config = self.read(rel_path, stack_group_config) stack_name = path.splitext(rel_path)[0] # Check for missing mandatory attributes for required_key in REQUIRED_KEYS: if required_key not in config: raise InvalidConfigFileError( "Required attribute '{0}' not found in configuration of '{1}'." .format(required_key, stack_name)) abs_template_path = path.join(self.context.project_path, self.context.templates_path, sceptreise_path(config["template_path"])) s3_details = self._collect_s3_details(stack_name, config) stack = Stack(name=stack_name, project_code=config["project_code"], template_path=abs_template_path, region=config["region"], template_bucket_name=config.get("template_bucket_name"), template_key_prefix=config.get("template_key_prefix"), required_version=config.get("required_version"), iam_role=config.get("iam_role"), profile=config.get("profile"), parameters=config.get("parameters", {}), sceptre_user_data=config.get("sceptre_user_data", {}), hooks=config.get("hooks", {}), s3_details=s3_details, dependencies=config.get("dependencies", []), role_arn=config.get("role_arn"), protected=config.get("protect", False), tags=config.get("stack_tags", {}), external_name=config.get("stack_name"), notifications=config.get("notifications"), on_failure=config.get("on_failure"), stack_timeout=config.get("stack_timeout", 0), stack_group_config=parsed_stack_group_config) del self.templating_vars["stack_group_config"] return stack
def test_external_name_with_custom_stack_name(self): stack = Stack(name="stack_name", project_code="project_code", template_path="template_path", region="region", external_name="external_name") assert stack.external_name == "external_name"
def setup_method(self, test_method, mock_config): self.mock_environment_config = MagicMock(spec=Config) self.mock_environment_config.environment_path = sentinel.path # environment config is an object which inherits from dict. Its # attributes are accessable via dot and square bracket notation. # In order to mimic the behaviour of the square bracket notation, # a side effect is used to return the expected value from the call to # __getitem__ that the square bracket notation makes. self.mock_environment_config.__getitem__.side_effect = [ sentinel.project_code, sentinel.region ] self.mock_connection_manager = Mock() self.stack = Stack(name="stack_name", environment_config=self.mock_environment_config, connection_manager=self.mock_connection_manager) # Set default value for stack properties self.stack._external_name = sentinel.external_name
def run(self): """ run is the method called by Sceptre. It should carry out the work intended by this hook. self.argument is available from the base class and contains the argument defined in the sceptre config file (see below) The following attributes may be available from the base class: self.stack_config (A dict of data from <stack_name>.yaml) self.environment_config (A dict of data from config.yaml) self.connection_manager (A connection_manager) """ environment = self.environment_config.environment_path + "/" + self.stack_config.name stack = Stack(name=environment, environment_config=self.environment_config, connection_manager=self.connection_manager) outputs = stack.describe_outputs() if outputs: core_artifacts_s3_bucket = self.stack_config['parameters']['CoreBootStrapRepositoryS3BucketName'] print(core_artifacts_s3_bucket) client_artifacts_s3_bucket = [output['OutputValue'] for output in outputs if output['OutputKey'] == 'EnvironmentArtifactsS3Bucket'] print(client_artifacts_s3_bucket[0]) bootstrap_artifacts_key = "bootstrap/" s3 = boto3.resource('s3') source_bucket = s3.Bucket(core_artifacts_s3_bucket) destination_bucket = s3.Bucket(client_artifacts_s3_bucket[0]) print(source_bucket) print(destination_bucket) for s3_object in source_bucket.objects.filter(Prefix=bootstrap_artifacts_key): destination_key = s3_object.key print(destination_key) s3.Object(destination_bucket.name, destination_key).copy_from(CopySource={ 'Bucket': s3_object.bucket_name, 'Key': s3_object.key})
def setup_method(self, test_method): self.patcher_connection_manager = patch( "sceptre.stack.ConnectionManager") self.mock_ConnectionManager = self.patcher_connection_manager.start() self.stack = Stack(name=sentinel.stack_name, project_code=sentinel.project_code, template_path=sentinel.template_path, region=sentinel.region, iam_role=sentinel.iam_role, parameters={"key1": "val1"}, sceptre_user_data=sentinel.sceptre_user_data, hooks={}, s3_details=None, dependencies=sentinel.dependencies, role_arn=sentinel.role_arn, protected=False, tags={"tag1": "val1"}, external_name=sentinel.external_name, notifications=[sentinel.notification], on_failure=sentinel.on_failure) self.stack._template = MagicMock(spec=Template)
def _construct_stack(self, rel_path, stack_group_config=None): """ Constructs an individual Stack object from a config path and a base config. :param rel_path: A relative config file path. :type rel_path: str :param stack_group_config: The Stack group config to use as defaults. :type stack_group_config: dict :returns: Stack object :rtype: sceptre.stack.Stack """ directory, filename = path.split(rel_path) if filename == self.context.config_file: pass self.templating_vars["stack_group_config"] = stack_group_config config = self.read(rel_path, stack_group_config) stack_name = path.splitext(rel_path)[0] abs_template_path = path.join(self.context.project_path, self.context.templates_path, config["template_path"]) s3_details = self._collect_s3_details(stack_name, config) stack = Stack(name=stack_name, project_code=config["project_code"], template_path=abs_template_path, region=config["region"], template_bucket_name=config.get("template_bucket_name"), template_key_prefix=config.get("template_key_prefix"), required_version=config.get("required_version"), profile=config.get("profile"), parameters=config.get("parameters", {}), sceptre_user_data=config.get("sceptre_user_data", {}), hooks=config.get("hooks", {}), s3_details=s3_details, dependencies=config.get("dependencies", []), role_arn=config.get("role_arn"), protected=config.get("protect", False), tags=config.get("stack_tags", {}), external_name=config.get("stack_name"), notifications=config.get("notifications"), on_failure=config.get("on_failure"), stack_timeout=config.get("stack_timeout", 0)) del self.templating_vars["stack_group_config"] return stack
def setup_method(self, test_method): self.patcher_SceptrePlanner = patch("sceptre.plan.plan.SceptrePlanner") self.stack = Stack(name=sentinel.stack_name, project_code=sentinel.project_code, template_path=sentinel.template_path, region=sentinel.region, profile=sentinel.profile, parameters={"key1": "val1"}, sceptre_user_data=sentinel.sceptre_user_data, hooks={}, s3_details=None, dependencies=sentinel.dependencies, role_arn=sentinel.role_arn, protected=False, tags={"tag1": "val1"}, external_name=sentinel.external_name, notifications=[sentinel.notification], on_failure=sentinel.on_failure, stack_timeout=sentinel.stack_timeout) self.stack_group = StackGroup(path="path", options=sentinel.options)
def test_initialiser_calls_correct_methods(self, mock_config): mock_config.get.return_value = sentinel.hooks self.stack._config = { "parameters": sentinel.parameters, "hooks": sentinel.hooks } self.mock_environment_config = MagicMock(spec=Config) self.mock_environment_config.environment_path = sentinel.path # environment config is an object which inherits from dict. Its # attributes are accessable via dot and square bracket notation. # In order to mimic the behaviour of the square bracket notation, # a side effect is used to return the expected value from the call to # __getitem__ that the square bracket notation makes. self.mock_environment_config.__getitem__.side_effect = [ sentinel.project_code, sentinel.template_bucket_name, sentinel.region ] Stack(name=sentinel.name, environment_config=self.mock_environment_config, connection_manager=sentinel.connection_manager)
def setup_method(self, test_method, mock_config): self.mock_environment_config = MagicMock(spec=Config) self.mock_environment_config.environment_path = sentinel.path # environment config is an object which inherits from dict. Its # attributes are accessable via dot and square bracket notation. # In order to mimic the behaviour of the square bracket notation, # a side effect is used to return the expected value from the call to # __getitem__ that the square bracket notation makes. self.mock_environment_config.__getitem__.side_effect = [ sentinel.project_code, sentinel.region ] self.mock_connection_manager = Mock() self.stack = Stack( name="stack_name", environment_config=self.mock_environment_config, connection_manager=self.mock_connection_manager ) # Set default value for stack properties self.stack._external_name = sentinel.external_name
def test_initiate_stack(self): stack = Stack(name=sentinel.stack_name, project_code=sentinel.project_code, template_path=sentinel.template_path, region=sentinel.region, external_name=sentinel.external_name) self.mock_ConnectionManager.assert_called_with(sentinel.region, None) assert stack.name == sentinel.stack_name assert stack.project_code == sentinel.project_code assert stack.external_name == sentinel.external_name assert stack.hooks == {} assert stack.parameters == {} assert stack.sceptre_user_data == {} assert stack.template_path == sentinel.template_path assert stack.s3_details is None assert stack._template is None assert stack.protected is False assert stack.role_arn is None assert stack.dependencies == [] assert stack.tags == {} assert stack.notifications == [] assert stack.on_failure is None
def run(self): """ run is the method called by Sceptre. It should carry out the work intended by this hook. self.argument is available from the base class and contains the argument defined in the sceptre config file (see below) The following attributes may be available from the base class: self.stack_config (A dict of data from <stack_name>.yaml) self.environment_config (A dict of data from config.yaml) self.connection_manager (A connection_manager) """ environment = self.environment_config.environment_path + "/" + self.stack_config.name stack = Stack(name=environment, environment_config=self.environment_config, connection_manager=self.connection_manager) outputs = stack.describe_outputs() print(outputs) if outputs: eks_cluster_name = [output['OutputValue'] for output in outputs if output['OutputKey'] == 'EKSClusterName'] print(eks_cluster_name[0]) connect_to_cluster_cmd = "aws eks update-kubeconfig --name {}".format(eks_cluster_name[0]) os.system(connect_to_cluster_cmd) worker_node_instance_role_arn = [output['OutputValue'] for output in outputs if output['OutputKey'] == 'WorkerNodeInstanceRoleArn'] print(worker_node_instance_role_arn[0]) cluster_admin_role_arn = [output['OutputValue'] for output in outputs if output['OutputKey'] == 'EKSClusterRoleArn'] print(cluster_admin_role_arn[0]) cluster_admin_role = [output['OutputValue'] for output in outputs if output['OutputKey'] == 'EKSClusterRole'] print(cluster_admin_role[0]) basepath = path.dirname(__file__) print(basepath) config_map_yaml_path = path.abspath(path.join(basepath, "ss_eks_config_map.yaml")) print(config_map_yaml_path) basepath = path.dirname(__file__) print(basepath) config_map_template_path = path.abspath(path.join(basepath, "ss_eks_config_map.j2.template")) print(config_map_template_path) j2_env = Environment(loader=FileSystemLoader(basepath), trim_blocks=True) template = j2_env.get_template("ss_eks_config_map.j2.template") render_values = {"worker_role_arn": worker_node_instance_role_arn[0], "EC2PrivateDNSName": "{{EC2PrivateDNSName}}", "cluster_admin_role_arn": cluster_admin_role_arn[0], "cluster_admin_role": cluster_admin_role[0]} rendered = template.render(render_values) with open('hooks/ss_eks_config_map.yaml', 'w') as f: f.write(rendered) connect_worker_nodes_to_cluster_cmd = "kubectl apply -f {}".format(config_map_yaml_path) os.system(connect_worker_nodes_to_cluster_cmd) get_aws_auth_configmap_cmd = "kubectl get configmaps aws-auth -o yaml --namespace='kube-system'" os.system(get_aws_auth_configmap_cmd) worker_node_autoscaling_group_name = [output['OutputValue'] for output in outputs if output['OutputKey'] == 'WorkerNodeAutoScalingGroupName'] print(worker_node_autoscaling_group_name[0]) autoscaling = boto3.client('autoscaling') autoscaling_group_response = autoscaling.describe_auto_scaling_groups( AutoScalingGroupNames=[worker_node_autoscaling_group_name[0]]) asg_desired_capacity = autoscaling_group_response['AutoScalingGroups'][0]['DesiredCapacity'] ready_nodes = 0 ready_nodes_current_loop = 0 print(asg_desired_capacity) print("Begin Polling for new Ready Worker Nodes...") while ready_nodes != asg_desired_capacity: print("Pause for 30 seconds between polling events...") sleep(5) print("Desired Capacity is: " + str(asg_desired_capacity)) print("Ready Worker Node Count is: " + str(ready_nodes)) get_node_status_cmd = "kubectl get nodes -o json" current_nodes_status = os.popen(get_node_status_cmd).read() current_nodes_status_json = json.loads(current_nodes_status) nodes = current_nodes_status_json['items'] for node in nodes: node_conditions = node['status']['conditions'] print(node_conditions) for condition in node_conditions: if condition['reason'] == 'KubeletReady': if condition['type'] == 'Ready' and condition['status'] == 'True': ready_nodes_current_loop += 1 ready_nodes = ready_nodes_current_loop ready_nodes_current_loop = 0 print("Desired Capacity is: " + str(asg_desired_capacity)) print("Ready Worker Node Count is: " + str(ready_nodes)) print("All Worker Nodes are Ready!")
class TestStack(object): def setup_method(self, test_method): self.patcher_connection_manager = patch( "sceptre.stack.ConnectionManager") self.mock_ConnectionManager = self.patcher_connection_manager.start() self.stack = Stack(name=sentinel.stack_name, project_code=sentinel.project_code, template_path=sentinel.template_path, region=sentinel.region, iam_role=sentinel.iam_role, parameters={"key1": "val1"}, sceptre_user_data=sentinel.sceptre_user_data, hooks={}, s3_details=None, dependencies=sentinel.dependencies, role_arn=sentinel.role_arn, protected=False, tags={"tag1": "val1"}, external_name=sentinel.external_name, notifications=[sentinel.notification], on_failure=sentinel.on_failure, stack_timeout=sentinel.stack_timeout) self.stack._template = MagicMock(spec=Template) def teardown_method(self, test_method): self.patcher_connection_manager.stop() def test_initiate_stack(self): stack = Stack(name=sentinel.stack_name, project_code=sentinel.project_code, template_path=sentinel.template_path, region=sentinel.region, external_name=sentinel.external_name) self.mock_ConnectionManager.assert_called_with(sentinel.region, None) assert stack.name == sentinel.stack_name assert stack.project_code == sentinel.project_code assert stack.external_name == sentinel.external_name assert stack.hooks == {} assert stack.parameters == {} assert stack.sceptre_user_data == {} assert stack.template_path == sentinel.template_path assert stack.s3_details is None assert stack._template is None assert stack.protected is False assert stack.role_arn is None assert stack.dependencies == [] assert stack.tags == {} assert stack.notifications == [] assert stack.on_failure is None def test_repr(self): self.stack.connection_manager.region = sentinel.region self.stack.connection_manager.iam_role = None assert self.stack.__repr__() == \ "sceptre.stack.Stack(" \ "name='sentinel.stack_name', " \ "project_code='sentinel.project_code', " \ "template_path='sentinel.template_path', " \ "region='sentinel.region', " \ "iam_role='None', parameters='{'key1': 'val1'}', " \ "sceptre_user_data='sentinel.sceptre_user_data', " \ "hooks='{}', s3_details='None', " \ "dependencies='sentinel.dependencies', "\ "role_arn='sentinel.role_arn', " \ "protected='False', tags='{'tag1': 'val1'}', " \ "external_name='sentinel.external_name', " \ "notifications='[sentinel.notification]', " \ "on_failure='sentinel.on_failure', " \ "stack_timeout='sentinel.stack_timeout'" \ ")" @patch("sceptre.stack.Template") def test_template_loads_template(self, mock_Template): self.stack._template = None mock_Template.return_value = sentinel.template response = self.stack.template mock_Template.assert_called_once_with( path=sentinel.template_path, sceptre_user_data=sentinel.sceptre_user_data, connection_manager=self.stack.connection_manager, s3_details=None) assert response == sentinel.template def test_template_returns_template_if_it_exists(self): self.stack._template = sentinel.template response = self.stack.template assert response == sentinel.template def test_external_name_with_custom_stack_name(self): stack = Stack(name="stack_name", project_code="project_code", template_path="template_path", region="region", external_name="external_name") assert stack.external_name == "external_name" @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack._get_stack_timeout") def test_create_sends_correct_request(self, mock_get_stack_timeout, mock_wait_for_completion): self.stack._template.get_boto_call_parameter.return_value = { "Template": sentinel.template } mock_get_stack_timeout.return_value = { "TimeoutInMinutes": sentinel.timeout } self.stack.create() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": [{ "ParameterKey": "key1", "ParameterValue": "val1" }], "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [sentinel.notification], "Tags": [{ "Key": "tag1", "Value": "val1" }], "OnFailure": sentinel.on_failure, "TimeoutInMinutes": sentinel.timeout }) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack._wait_for_completion") def test_create_sends_correct_request_no_notifications( self, mock_wait_for_completion): self.stack._template = Mock(spec=Template) self.stack._template.get_boto_call_parameter.return_value = { "Template": sentinel.template } self.stack.notifications = [] self.stack.create() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": [{ "ParameterKey": "key1", "ParameterValue": "val1" }], "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [], "Tags": [{ "Key": "tag1", "Value": "val1" }], "OnFailure": sentinel.on_failure, "TimeoutInMinutes": sentinel.stack_timeout }) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack._wait_for_completion") def test_create_sends_correct_request_with_no_failure_no_timeout( self, mock_wait_for_completion): self.stack._template.get_boto_call_parameter.return_value = { "Template": sentinel.template } self.stack.on_failure = None self.stack.stack_timeout = 0 self.stack.create() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": [{ "ParameterKey": "key1", "ParameterValue": "val1" }], "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [sentinel.notification], "Tags": [{ "Key": "tag1", "Value": "val1" }] }) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack._wait_for_completion") def test_update_sends_correct_request(self, mock_wait_for_completion): self.stack._template = Mock(spec=Template) self.stack._template.get_boto_call_parameter.return_value = { "Template": sentinel.template } self.stack.update() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="update_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": [{ "ParameterKey": "key1", "ParameterValue": "val1" }], "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [sentinel.notification], "Tags": [{ "Key": "tag1", "Value": "val1" }] }) mock_wait_for_completion.assert_called_once_with( sentinel.stack_timeout) @patch("sceptre.stack.Stack._wait_for_completion") def test_update_cancels_after_timeout(self, mock_wait_for_completion): self.stack._template = Mock(spec=Template) self.stack._template.get_boto_call_parameter.return_value = { "Template": sentinel.template } mock_wait_for_completion.return_value = StackStatus.IN_PROGRESS self.stack.update() calls = [ call(service="cloudformation", command="update_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": [{ "ParameterKey": "key1", "ParameterValue": "val1" }], "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [sentinel.notification], "Tags": [{ "Key": "tag1", "Value": "val1" }] }), call(service="cloudformation", command="cancel_update_stack", kwargs={"StackName": sentinel.external_name}) ] self.stack.connection_manager.call.assert_has_calls(calls) mock_wait_for_completion.assert_has_calls( [call(sentinel.stack_timeout), call()]) @patch("sceptre.stack.Stack._wait_for_completion") def test_update_sends_correct_request_no_notification( self, mock_wait_for_completion): self.stack._template = Mock(spec=Template) self.stack._template.get_boto_call_parameter.return_value = { "Template": sentinel.template } self.stack.notifications = [] self.stack.update() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="update_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": [{ "ParameterKey": "key1", "ParameterValue": "val1" }], "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [], "Tags": [{ "Key": "tag1", "Value": "val1" }] }) mock_wait_for_completion.assert_called_once_with( sentinel.stack_timeout) @patch("sceptre.stack.Stack._wait_for_completion") def test_cancel_update_sends_correct_request(self, mock_wait_for_completion): self.stack.cancel_stack_update() self.stack.connection_manager.call.assert_called_once_with( service="cloudformation", command="cancel_update_stack", kwargs={"StackName": sentinel.external_name}) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack.create") @patch("sceptre.stack.Stack.get_status") def test_launch_with_stack_that_does_not_exist(self, mock_get_status, mock_create): mock_get_status.side_effect = StackDoesNotExistError() mock_create.return_value = sentinel.launch_response response = self.stack.launch() mock_create.assert_called_once_with() assert response == sentinel.launch_response @patch("sceptre.stack.Stack.create") @patch("sceptre.stack.Stack.delete") @patch("sceptre.stack.Stack.get_status") def test_launch_with_stack_that_failed_to_create(self, mock_get_status, mock_delete, mock_create): mock_get_status.return_value = "CREATE_FAILED" mock_create.return_value = sentinel.launch_response response = self.stack.launch() mock_delete.assert_called_once_with() mock_create.assert_called_once_with() assert response == sentinel.launch_response @patch("sceptre.stack.Stack.update") @patch("sceptre.stack.Stack.get_status") def test_launch_with_complete_stack_with_updates_to_perform( self, mock_get_status, mock_update): mock_get_status.return_value = "CREATE_COMPLETE" mock_update.return_value = sentinel.launch_response response = self.stack.launch() mock_update.assert_called_once_with() assert response == sentinel.launch_response @patch("sceptre.stack.Stack.update") @patch("sceptre.stack.Stack.get_status") def test_launch_with_complete_stack_with_no_updates_to_perform( self, mock_get_status, mock_update): mock_get_status.return_value = "CREATE_COMPLETE" mock_update.side_effect = ClientError( { "Error": { "Code": "NoUpdateToPerformError", "Message": "No updates are to be performed." } }, sentinel.operation) response = self.stack.launch() mock_update.assert_called_once_with() assert response == StackStatus.COMPLETE @patch("sceptre.stack.Stack.update") @patch("sceptre.stack.Stack.get_status") def test_launch_with_complete_stack_with_unknown_client_error( self, mock_get_status, mock_update): mock_get_status.return_value = "CREATE_COMPLETE" mock_update.side_effect = ClientError( {"Error": { "Code": "Boom!", "Message": "Boom!" }}, sentinel.operation) with pytest.raises(ClientError): self.stack.launch() @patch("sceptre.stack.Stack.get_status") def test_launch_with_in_progress_stack(self, mock_get_status): mock_get_status.return_value = "CREATE_IN_PROGRESS" response = self.stack.launch() assert response == StackStatus.IN_PROGRESS @patch("sceptre.stack.Stack.get_status") def test_launch_with_failed_stack(self, mock_get_status): mock_get_status.return_value = "UPDATE_FAILED" with pytest.raises(CannotUpdateFailedStackError): response = self.stack.launch() assert response == StackStatus.FAILED @patch("sceptre.stack.Stack.get_status") def test_launch_with_unknown_stack_status(self, mock_get_status): mock_get_status.return_value = "UNKNOWN_STATUS" with pytest.raises(UnknownStackStatusError): self.stack.launch() @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.get_status") def test_delete_with_created_stack(self, mock_get_status, mock_wait_for_completion): mock_get_status.return_value = "CREATE_COMPLETE" self.stack.delete() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="delete_stack", kwargs={ "StackName": sentinel.external_name, "RoleARN": sentinel.role_arn }) @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.get_status") def test_delete_when_wait_for_completion_raises_stack_does_not_exist_error( self, mock_get_status, mock_wait_for_completion): mock_get_status.return_value = "CREATE_COMPLETE" mock_wait_for_completion.side_effect = StackDoesNotExistError() status = self.stack.delete() assert status == StackStatus.COMPLETE @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.get_status") def test_delete_when_wait_for_completion_raises_non_existent_client_error( self, mock_get_status, mock_wait_for_completion): mock_get_status.return_value = "CREATE_COMPLETE" mock_wait_for_completion.side_effect = ClientError( { "Error": { "Code": "DoesNotExistException", "Message": "Stack does not exist" } }, sentinel.operation) status = self.stack.delete() assert status == StackStatus.COMPLETE @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.get_status") def test_delete_when_wait_for_completion_raises_unexpected_client_error( self, mock_get_status, mock_wait_for_completion): mock_get_status.return_value = "CREATE_COMPLETE" mock_wait_for_completion.side_effect = ClientError( {"Error": { "Code": "DoesNotExistException", "Message": "Boom" }}, sentinel.operation) with pytest.raises(ClientError): self.stack.delete() @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.get_status") def test_delete_with_non_existent_stack(self, mock_get_status, mock_wait_for_completion): mock_get_status.side_effect = StackDoesNotExistError() status = self.stack.delete() assert status == StackStatus.COMPLETE def test_describe_stack_sends_correct_request(self): self.stack.describe() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_stacks", kwargs={"StackName": sentinel.external_name}) def test_describe_events_sends_correct_request(self): self.stack.describe_events() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_stack_events", kwargs={"StackName": sentinel.external_name}) def test_describe_resources_sends_correct_request(self): self.stack.connection_manager.call.return_value = { "StackResources": [{ "LogicalResourceId": sentinel.logical_resource_id, "PhysicalResourceId": sentinel.physical_resource_id, "OtherParam": sentinel.other_param }] } response = self.stack.describe_resources() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_stack_resources", kwargs={"StackName": sentinel.external_name}) assert response == [{ "LogicalResourceId": sentinel.logical_resource_id, "PhysicalResourceId": sentinel.physical_resource_id }] @patch("sceptre.stack.Stack.describe") def test_describe_outputs_sends_correct_request(self, mock_describe): mock_describe.return_value = { "Stacks": [{ "Outputs": sentinel.outputs }] } response = self.stack.describe_outputs() mock_describe.assert_called_once_with() assert response == sentinel.outputs @patch("sceptre.stack.Stack.describe") def test_describe_outputs_handles_stack_with_no_outputs( self, mock_describe): mock_describe.return_value = {"Stacks": [{}]} response = self.stack.describe_outputs() assert response == [] def test_continue_update_rollback_sends_correct_request(self): self.stack.continue_update_rollback() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="continue_update_rollback", kwargs={ "StackName": sentinel.external_name, "RoleARN": sentinel.role_arn }) def test_set_stack_policy_sends_correct_request(self): self.stack.set_policy("tests/fixtures/stack_policies/unlock.json") self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="set_stack_policy", kwargs={ "StackName": sentinel.external_name, "StackPolicyBody": """{ "Statement" : [ { "Effect" : "Allow", "Action" : "Update:*", "Principal": "*", "Resource" : "*" } ] } """ }) def test_get_stack_policy_sends_correct_request(self): self.stack.get_policy() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="get_stack_policy", kwargs={"StackName": sentinel.external_name}) def test_create_change_set_sends_correct_request(self): self.stack._template.get_boto_call_parameter.return_value = { "Template": sentinel.template } self.stack.create_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_change_set", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": [{ "ParameterKey": "key1", "ParameterValue": "val1" }], "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "ChangeSetName": sentinel.change_set_name, "RoleARN": sentinel.role_arn, "NotificationARNs": [sentinel.notification], "Tags": [{ "Key": "tag1", "Value": "val1" }] }) def test_create_change_set_sends_correct_request_no_notifications(self): self.stack._template = Mock(spec=Template) self.stack._template.get_boto_call_parameter.return_value = { "Template": sentinel.template } self.stack.notifications = [] self.stack.create_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_change_set", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": [{ "ParameterKey": "key1", "ParameterValue": "val1" }], "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "ChangeSetName": sentinel.change_set_name, "RoleARN": sentinel.role_arn, "NotificationARNs": [], "Tags": [{ "Key": "tag1", "Value": "val1" }] }) def test_delete_change_set_sends_correct_request(self): self.stack.delete_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="delete_change_set", kwargs={ "ChangeSetName": sentinel.change_set_name, "StackName": sentinel.external_name }) def test_describe_change_set_sends_correct_request(self): self.stack.describe_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_change_set", kwargs={ "ChangeSetName": sentinel.change_set_name, "StackName": sentinel.external_name }) @patch("sceptre.stack.Stack._wait_for_completion") def test_execute_change_set_sends_correct_request( self, mock_wait_for_completion): self.stack.execute_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="execute_change_set", kwargs={ "ChangeSetName": sentinel.change_set_name, "StackName": sentinel.external_name }) mock_wait_for_completion.assert_called_once_with() def test_list_change_sets_sends_correct_request(self): self.stack.list_change_sets() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="list_change_sets", kwargs={"StackName": sentinel.external_name}) @patch("sceptre.stack.Stack.set_policy") @patch("os.path.join") def test_lock_calls_set_stack_policy_with_policy(self, mock_join, mock_set_policy): mock_join.return_value = "tests/fixtures/stack_policies/lock.json" self.stack.lock() mock_set_policy.assert_called_once_with( "tests/fixtures/stack_policies/lock.json") @patch("sceptre.stack.Stack.set_policy") @patch("os.path.join") def test_unlock_calls_set_stack_policy_with_policy(self, mock_join, mock_set_policy): mock_join.return_value = "tests/fixtures/stack_policies/unlock.json" self.stack.unlock() mock_set_policy.assert_called_once_with( "tests/fixtures/stack_policies/unlock.json") def test_format_parameters_with_sting_values(self): parameters = {"key1": "value1", "key2": "value2", "key3": "value3"} formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted(formatted_parameters, key=lambda x: x["ParameterKey"]) assert sorted_formatted_parameters == [{ "ParameterKey": "key1", "ParameterValue": "value1" }, { "ParameterKey": "key2", "ParameterValue": "value2" }, { "ParameterKey": "key3", "ParameterValue": "value3" }] def test_format_parameters_with_none_values(self): parameters = {"key1": None, "key2": None, "key3": None} formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted(formatted_parameters, key=lambda x: x["ParameterKey"]) assert sorted_formatted_parameters == [] def test_format_parameters_with_none_and_string_values(self): parameters = {"key1": "value1", "key2": None, "key3": "value3"} formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted(formatted_parameters, key=lambda x: x["ParameterKey"]) assert sorted_formatted_parameters == [{ "ParameterKey": "key1", "ParameterValue": "value1" }, { "ParameterKey": "key3", "ParameterValue": "value3" }] def test_format_parameters_with_list_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": ["value4", "value5", "value6"], "key3": ["value7", "value8", "value9"] } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted(formatted_parameters, key=lambda x: x["ParameterKey"]) assert sorted_formatted_parameters == [{ "ParameterKey": "key1", "ParameterValue": "value1,value2,value3" }, { "ParameterKey": "key2", "ParameterValue": "value4,value5,value6" }, { "ParameterKey": "key3", "ParameterValue": "value7,value8,value9" }] def test_format_parameters_with_none_and_list_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": None, "key3": ["value7", "value8", "value9"] } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted(formatted_parameters, key=lambda x: x["ParameterKey"]) assert sorted_formatted_parameters == [{ "ParameterKey": "key1", "ParameterValue": "value1,value2,value3" }, { "ParameterKey": "key3", "ParameterValue": "value7,value8,value9" }] def test_format_parameters_with_list_and_string_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": "value4", "key3": ["value5", "value6", "value7"] } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted(formatted_parameters, key=lambda x: x["ParameterKey"]) assert sorted_formatted_parameters == [{ "ParameterKey": "key1", "ParameterValue": "value1,value2,value3" }, { "ParameterKey": "key2", "ParameterValue": "value4" }, { "ParameterKey": "key3", "ParameterValue": "value5,value6,value7" }] def test_format_parameters_with_none_list_and_string_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": "value4", "key3": None } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted(formatted_parameters, key=lambda x: x["ParameterKey"]) assert sorted_formatted_parameters == [ { "ParameterKey": "key1", "ParameterValue": "value1,value2,value3" }, { "ParameterKey": "key2", "ParameterValue": "value4" }, ] @patch("sceptre.stack.Stack.describe") def test_get_status_with_created_stack(self, mock_describe): mock_describe.return_value = { "Stacks": [{ "StackStatus": "CREATE_COMPLETE" }] } status = self.stack.get_status() assert status == "CREATE_COMPLETE" @patch("sceptre.stack.Stack.describe") def test_get_status_with_non_existent_stack(self, mock_describe): mock_describe.side_effect = ClientError( { "Error": { "Code": "DoesNotExistException", "Message": "Stack does not exist" } }, sentinel.operation) with pytest.raises(StackDoesNotExistError): self.stack.get_status() @patch("sceptre.stack.Stack.describe") def test_get_status_with_unknown_clinet_error(self, mock_describe): mock_describe.side_effect = ClientError( {"Error": { "Code": "DoesNotExistException", "Message": "Boom!" }}, sentinel.operation) with pytest.raises(ClientError): self.stack.get_status() def test_get_role_arn_without_role(self): self.stack.role_arn = None assert self.stack._get_role_arn() == {} def test_get_role_arn_with_role(self): assert self.stack._get_role_arn() == {"RoleARN": sentinel.role_arn} def test_protect_execution_without_protection(self): # Function should do nothing if protect == False self.stack._protect_execution() def test_protect_execution_without_explicit_protection(self): self.stack._protect_execution() def test_protect_execution_with_protection(self): self.stack.protected = True with pytest.raises(ProtectedStackError): self.stack._protect_execution() @patch("sceptre.stack.time") @patch("sceptre.stack.Stack._log_new_events") @patch("sceptre.stack.Stack.get_status") @patch("sceptre.stack.Stack._get_simplified_status") def test_wait_for_completion_calls_log_new_events( self, mock_get_simplified_status, mock_get_status, mock_log_new_events, mock_time): mock_get_simplified_status.return_value = StackStatus.COMPLETE self.stack._wait_for_completion() mock_log_new_events.assert_called_once_with() @pytest.mark.parametrize("test_input,expected", [("ROLLBACK_COMPLETE", StackStatus.FAILED), ("STACK_COMPLETE", StackStatus.COMPLETE), ("STACK_IN_PROGRESS", StackStatus.IN_PROGRESS), ("STACK_FAILED", StackStatus.FAILED)]) def test_get_simplified_status_with_known_stack_statuses( self, test_input, expected): response = self.stack._get_simplified_status(test_input) assert response == expected def test_get_simplified_status_with_stack_in_unknown_state(self): with pytest.raises(UnknownStackStatusError): self.stack._get_simplified_status("UNKOWN_STATUS") @patch("sceptre.stack.Stack.describe_events") def test_log_new_events_calls_describe_events(self, mock_describe_events): mock_describe_events.return_value = {"StackEvents": []} self.stack._log_new_events() self.stack.describe_events.assert_called_once_with() @patch("sceptre.stack.Stack.describe_events") def test_log_new_events_prints_correct_event(self, mock_describe_events): self.stack.name = "stack-name" mock_describe_events.return_value = { "StackEvents": [{ "Timestamp": datetime.datetime(2016, 3, 15, 14, 2, 0, 0, tzinfo=tzutc()), "LogicalResourceId": "id-2", "ResourceType": "type-2", "ResourceStatus": "resource-status" }, { "Timestamp": datetime.datetime(2016, 3, 15, 14, 1, 0, 0, tzinfo=tzutc()), "LogicalResourceId": "id-1", "ResourceType": "type-1", "ResourceStatus": "resource", "ResourceStatusReason": "User Initiated" }] } self.stack.most_recent_event_datetime = (datetime.datetime( 2016, 3, 15, 14, 0, 0, 0, tzinfo=tzutc())) self.stack._log_new_events() @patch("sceptre.stack.time") @patch("sceptre.stack.Stack._get_cs_status") def test_wait_for_cs_completion_calls_get_cs_status( self, mock_get_cs_status, mock_time): mock_get_cs_status.side_effect = [ StackChangeSetStatus.PENDING, StackChangeSetStatus.READY ] self.stack.wait_for_cs_completion(sentinel.change_set_name) mock_get_cs_status.assert_called_with(sentinel.change_set_name) @patch("sceptre.stack.Stack.describe_change_set") def test_get_cs_status_handles_all_statuses(self, mock_describe_change_set): scss = StackChangeSetStatus return_values = { # NOQA "Status": ('CREATE_PENDING', 'CREATE_IN_PROGRESS', 'CREATE_COMPLETE', 'DELETE_COMPLETE', 'FAILED'), # NOQA "ExecutionStatus": { # NOQA 'UNAVAILABLE': (scss.PENDING, scss.PENDING, scss.PENDING, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'AVAILABLE': (scss.PENDING, scss.PENDING, scss.READY, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'EXECUTE_IN_PROGRESS': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'EXECUTE_COMPLETE': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'EXECUTE_FAILED': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'OBSOLETE': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA } # NOQA } # NOQA for i, status in enumerate(return_values['Status']): for exec_status, returns in \ return_values['ExecutionStatus'].items(): mock_describe_change_set.return_value = { "Status": status, "ExecutionStatus": exec_status } response = self.stack._get_cs_status(sentinel.change_set_name) assert response == returns[i] for status in return_values['Status']: mock_describe_change_set.return_value = { "Status": status, "ExecutionStatus": 'UNKOWN_STATUS' } with pytest.raises(UnknownStackChangeSetStatusError): self.stack._get_cs_status(sentinel.change_set_name) for exec_status in return_values['ExecutionStatus'].keys(): mock_describe_change_set.return_value = { "Status": 'UNKOWN_STATUS', "ExecutionStatus": exec_status } with pytest.raises(UnknownStackChangeSetStatusError): self.stack._get_cs_status(sentinel.change_set_name) mock_describe_change_set.return_value = { "Status": 'UNKOWN_STATUS', "ExecutionStatus": 'UNKOWN_STATUS', } with pytest.raises(UnknownStackChangeSetStatusError): self.stack._get_cs_status(sentinel.change_set_name) @patch("sceptre.stack.Stack.describe_change_set") def test_get_cs_status_raises_unexpected_exceptions( self, mock_describe_change_set): mock_describe_change_set.side_effect = ClientError( { "Error": { "Code": "ChangeSetNotFound", "Message": "ChangeSet [*] does not exist" } }, sentinel.operation) with pytest.raises(ClientError): self.stack._get_cs_status(sentinel.change_set_name)
def run(self): """ run is the method called by Sceptre. It should carry out the work intended by this hook. self.argument is available from the base class and contains the argument defined in the sceptre config file (see below) The following attributes may be available from the base class: self.stack_config (A dict of data from <stack_name>.yaml) self.environment_config (A dict of data from config.yaml) self.connection_manager (A connection_manager) """ environment = self.environment_config.environment_path + "/" + self.stack_config.name stack = Stack(name=environment, environment_config=self.environment_config, connection_manager=self.connection_manager) description = stack.describe() if description: rest_api_autoscaling_group_name = [parameter['ParameterValue'] for parameter in description['Stacks'][0]['Parameters'] if parameter['ParameterKey'] == 'RestApiAutoScalingGroupName'] print("AutoScaling-Group to be Refreshed: " + rest_api_autoscaling_group_name[0]) autoscaling = boto3.client('autoscaling') print("Begin refreshing API server instances...") sleep(3) autoscaling_group_response = autoscaling.describe_auto_scaling_groups(AutoScalingGroupNames=[rest_api_autoscaling_group_name[0]]) print("--------Begin AutoScaling-Group Describe Resources Response (Pre-Termination)----") print(autoscaling_group_response) print("--------End AutoScaling-Group Describe Resources Response (Pre-Termination)--------") current_instances = autoscaling_group_response['AutoScalingGroups'][0]['Instances'] for instance in current_instances: instance_id = instance['InstanceId'] print(instance_id) ec2 = boto3.client('ec2') waiter = ec2.get_waiter('instance_terminated') ec2.terminate_instances(InstanceIds=[instance_id]) print("Instance terminating...") waiter.wait(InstanceIds=[instance_id]) print("Instance successfully terminated!") print("All Instances terminated, wait 3 Minutes for AutoScaling-Group Details to refresh...") sleep(160) autoscaling_group_response = autoscaling.describe_auto_scaling_groups(AutoScalingGroupNames=[rest_api_autoscaling_group_name[0]]) print("--------Begin AutoScaling-Group Describe Resources Response (Post-Termination)----") print(autoscaling_group_response) print("--------End AutoScaling-Group Describe Resources Response (Post-Termination)--------") asg_desired_capacity = autoscaling_group_response['AutoScalingGroups'][0]['DesiredCapacity'] in_service_instances = 0 in_service_current_loop = 0 print("Begin Polling for new InService Instances...") while in_service_instances != asg_desired_capacity: print("Pause for 30 seconds between polling events...") sleep(30) print("Desired Capacity is: " + str(asg_desired_capacity)) print("InService Instance Count is: " + str(in_service_instances)) autoscaling_group_response = autoscaling.describe_auto_scaling_groups( AutoScalingGroupNames=[rest_api_autoscaling_group_name[0]]) current_instances = autoscaling_group_response['AutoScalingGroups'][0]['Instances'] for instance in current_instances: instance_id = instance['InstanceId'] print(instance_id) print("Instance Lifecycle State: " + instance['LifecycleState']) if instance['LifecycleState'] == "InService": in_service_current_loop += 1 in_service_instances = in_service_current_loop in_service_current_loop = 0 print("Desired Capacity is: " + str(asg_desired_capacity)) print("InService Instance Count is: " + str(in_service_instances)) print("All Instances are InService!")
def run(self): """ run is the method called by Sceptre. It should carry out the work intended by this hook. self.argument is available from the base class and contains the argument defined in the sceptre config file (see below) The following attributes may be available from the base class: self.stack_config (A dict of data from <stack_name>.yaml) self.environment_config (A dict of data from config.yaml) self.connection_manager (A connection_manager) """ action = self.argument print(action) print(self.stack_config) environment = self.environment_config.environment_path + "/" + self.stack_config.name kong_stack = Stack(name=self.environment_config.environment_path + "/kong", environment_config=self.environment_config, connection_manager=self.connection_manager) kong_outputs = kong_stack.describe_outputs() print(kong_outputs) restapi_stack = Stack(name=self.environment_config.environment_path + "/restapi", environment_config=self.environment_config, connection_manager=self.connection_manager) restapi_outputs = restapi_stack.describe_outputs() print(restapi_outputs) vpc_stack = Stack(name=self.environment_config.environment_path + "/vpc", environment_config=self.environment_config, connection_manager=self.connection_manager) vpc_outputs = vpc_stack.describe_outputs() print(vpc_outputs) exit if restapi_outputs: admin_cidr_block = [ output['OutputValue'] for output in vpc_outputs if output['OutputKey'] == 'AdminCidrBlock' ] print(admin_cidr_block[0]) local_public_ip = requests.get('http://ip.42.pl/raw').text local_public_ip = local_public_ip + "/32" print(local_public_ip) print(admin_cidr_block[0]) temporary_access = local_public_ip != admin_cidr_block[0] ec2 = boto3.client('ec2') # Call kong API requests if action == "configure": kong_public_load_balancer_security_group_id = [ output['OutputValue'] for output in kong_outputs if output['OutputKey'] == 'KongPublicLoadBalancerSecurityGroup' ] print(kong_public_load_balancer_security_group_id[0]) if temporary_access: self.handle_temporary_access( ec2, "authorize", kong_public_load_balancer_security_group_id[0], local_public_ip) kong_public_load_balancer_dns = [ output['OutputValue'] for output in kong_outputs if output['OutputKey'] == 'KongPublicLoadBalancerDNS' ] print(kong_public_load_balancer_dns[0]) restapi_private_load_balancer_dns = [ output['OutputValue'] for output in restapi_outputs if output['OutputKey'] == 'RestApiPrivateLoadBalancerDNS' ] print(restapi_private_load_balancer_dns[0]) env_artifacts_s3_bucket = [ output['OutputValue'] for output in vpc_outputs if output['OutputKey'] == 'EnvironmentArtifactsS3Bucket' ] print(env_artifacts_s3_bucket[0]) restapi_prefix = self.stack_config['parameters'][ 'RestApiPrefix'] print(restapi_prefix) oauth_admin_port = self.stack_config['parameters'][ 'OAuthAdminPort'] print(oauth_admin_port) postman_files_s3_key = self.stack_config['parameters'][ 'OAuthConfigurationFilesLocation'] print(postman_files_s3_key) # Download Kong Postman Files s3 = boto3.resource('s3') download_bucket = s3.Bucket(env_artifacts_s3_bucket[0]) for s3_object in download_bucket.objects.filter( Prefix=postman_files_s3_key): if s3_object.key[-1] == "/": continue download_key = s3_object.key local_postman_path = download_key.replace( postman_files_s3_key, 'hooks/') print(download_key) print(local_postman_path) s3.Bucket(env_artifacts_s3_bucket[0]).download_file( download_key, local_postman_path) # Update Kong Postman Environment File with open('hooks/kong.postman_environment.json', 'r') as f: json_data = json.load(f) for value in json_data['values']: if value['key'] == "konghost": value['value'] = kong_public_load_balancer_dns[0] if value['key'] == 'upstreamhost': value['value'] = restapi_private_load_balancer_dns[ 0] if value['key'] == 'adminport': value['value'] = oauth_admin_port if value['key'] == 'apiprefix': value['value'] = restapi_prefix with open('hooks/kong.postman_environment.json', 'w') as f: f.write(json.dumps(json_data)) # Execute Postman via Newman basepath = path.dirname(__file__) print(basepath) postman_collection_path = path.abspath( path.join(basepath, "kong.postman_collection.json")) print(postman_collection_path) postman_environment_path = path.abspath( path.join(basepath, "kong.postman_environment.json")) print(postman_environment_path) postman_response_json_path = path.abspath( path.join(basepath, "postman_response.json")) print(postman_response_json_path) cmd = "newman run {0} -e {1} --insecure -r cli,json --reporter-json-export {2}".format( postman_collection_path, postman_environment_path, postman_response_json_path) print(os.system(cmd)) # Parse Postman Response with open(postman_response_json_path) as f: postman_response_json_data = json.load(f) postman_executions = postman_response_json_data['run'][ 'executions'] postman_get_consumer_response = [ item['assertions'][0]['assertion'] for item in postman_executions if item['item']['name'] == 'Get Kong Consumer Info' ] postman_get_consumer_response_json = json.loads( postman_get_consumer_response[0]) self.stack_config['parameters']['OAuthRestApiKongConsumerClientId'] = \ postman_get_consumer_response_json['data'][0]['client_id'] print(self.stack_config['parameters'] ['OAuthRestApiKongConsumerClientId']) self.stack_config['parameters']['OAuthRestApiKongConsumerClientSecret'] = \ postman_get_consumer_response_json['data'][0]['client_secret'] print(self.stack_config['parameters'] ['OAuthRestApiKongConsumerClientSecret']) postman_get_oauth_info_response = [ item['assertions'][0]['assertion'] for item in postman_executions if item['item']['name'] == 'Get OAuth2 Info' ] postman_get_oauth_info_response_json = json.loads( postman_get_oauth_info_response[0]) self.stack_config['parameters']['OAuthRestApiProvisionKey'] = \ postman_get_oauth_info_response_json['data'][0]['config']['provision_key'] print(self.stack_config['parameters'] ['OAuthRestApiProvisionKey']) if temporary_access: self.handle_temporary_access( ec2, "revoke", kong_public_load_balancer_security_group_id[0], local_public_ip)
class TestStack(object): def setup_method(self, test_method): self.stack = Stack(name=sentinel.stack_name, project_code=sentinel.project_code, template_bucket_name=sentinel.template_bucket_name, template_key_prefix=sentinel.template_key_prefix, required_version=sentinel.required_version, template_path=sentinel.template_path, region=sentinel.region, profile=sentinel.profile, parameters={"key1": "val1"}, sceptre_user_data=sentinel.sceptre_user_data, hooks={}, s3_details=None, dependencies=sentinel.dependencies, role_arn=sentinel.role_arn, protected=False, tags={"tag1": "val1"}, external_name=sentinel.external_name, notifications=[sentinel.notification], on_failure=sentinel.on_failure, stack_timeout=sentinel.stack_timeout) self.stack._template = MagicMock(spec=Template) def test_initiate_stack(self): stack = Stack(name=sentinel.stack_name, project_code=sentinel.project_code, template_path=sentinel.template_path, template_bucket_name=sentinel.template_bucket_name, template_key_prefix=sentinel.template_key_prefix, required_version=sentinel.required_version, region=sentinel.region, external_name=sentinel.external_name) assert stack.name == sentinel.stack_name assert stack.project_code == sentinel.project_code assert stack.template_bucket_name == sentinel.template_bucket_name assert stack.template_key_prefix == sentinel.template_key_prefix assert stack.required_version == sentinel.required_version assert stack.external_name == sentinel.external_name assert stack.hooks == {} assert stack.parameters == {} assert stack.sceptre_user_data == {} assert stack.template_path == sentinel.template_path assert stack.s3_details is None assert stack._template is None assert stack.protected is False assert stack.role_arn is None assert stack.dependencies == [] assert stack.tags == {} assert stack.notifications == [] assert stack.on_failure is None def test_repr(self): assert self.stack.__repr__() == \ "sceptre.stack.Stack(" \ "name='sentinel.stack_name', " \ "project_code='sentinel.project_code', " \ "template_path='sentinel.template_path', " \ "region='sentinel.region', " \ "template_bucket_name='sentinel.template_bucket_name', "\ "template_key_prefix='sentinel.template_key_prefix', "\ "required_version='sentinel.required_version', "\ "profile='sentinel.profile', " \ "sceptre_user_data='sentinel.sceptre_user_data', " \ "parameters='{'key1': 'val1'}', "\ "hooks='{}', s3_details='None', " \ "dependencies='sentinel.dependencies', "\ "role_arn='sentinel.role_arn', " \ "protected='False', tags='{'tag1': 'val1'}', " \ "external_name='sentinel.external_name', " \ "notifications='[sentinel.notification]', " \ "on_failure='sentinel.on_failure', " \ "stack_timeout='sentinel.stack_timeout'" \ ")"
class TestStack(object): def setup_method(self, test_method): self.stack = Stack(name='dev/app/stack', project_code=sentinel.project_code, template_bucket_name=sentinel.template_bucket_name, template_key_prefix=sentinel.template_key_prefix, required_version=sentinel.required_version, template_path=sentinel.template_path, region=sentinel.region, profile=sentinel.profile, parameters={"key1": "val1"}, sceptre_user_data=sentinel.sceptre_user_data, hooks={}, s3_details=None, dependencies=sentinel.dependencies, role_arn=sentinel.role_arn, protected=False, tags={"tag1": "val1"}, external_name=sentinel.external_name, notifications=[sentinel.notification], on_failure=sentinel.on_failure, stack_timeout=sentinel.stack_timeout, stack_group_config={}) self.stack._template = MagicMock(spec=Template) def test_initiate_stack(self): stack = Stack(name='dev/stack/app', project_code=sentinel.project_code, template_path=sentinel.template_path, template_bucket_name=sentinel.template_bucket_name, template_key_prefix=sentinel.template_key_prefix, required_version=sentinel.required_version, region=sentinel.region, external_name=sentinel.external_name) assert stack.name == 'dev/stack/app' assert stack.project_code == sentinel.project_code assert stack.template_bucket_name == sentinel.template_bucket_name assert stack.template_key_prefix == sentinel.template_key_prefix assert stack.required_version == sentinel.required_version assert stack.external_name == sentinel.external_name assert stack.hooks == {} assert stack.parameters == {} assert stack.sceptre_user_data == {} assert stack.template_path == sentinel.template_path assert stack.s3_details is None assert stack._template is None assert stack.protected is False assert stack.role_arn is None assert stack.dependencies == [] assert stack.tags == {} assert stack.notifications == [] assert stack.on_failure is None assert stack.stack_group_config == {} def test_stack_repr(self): assert self.stack.__repr__() == \ "sceptre.stack.Stack(" \ "name='dev/app/stack', " \ "project_code=sentinel.project_code, " \ "template_path=sentinel.template_path, " \ "region=sentinel.region, " \ "template_bucket_name=sentinel.template_bucket_name, "\ "template_key_prefix=sentinel.template_key_prefix, "\ "required_version=sentinel.required_version, "\ "profile=sentinel.profile, " \ "sceptre_user_data=sentinel.sceptre_user_data, " \ "parameters={'key1': 'val1'}, "\ "hooks={}, "\ "s3_details=None, " \ "dependencies=sentinel.dependencies, "\ "role_arn=sentinel.role_arn, "\ "protected=False, "\ "tags={'tag1': 'val1'}, "\ "external_name=sentinel.external_name, " \ "notifications=[sentinel.notification], " \ "on_failure=sentinel.on_failure, " \ "stack_timeout=sentinel.stack_timeout, " \ "stack_group_config={}" \ ")" def test_repr_can_eval_correctly(self): sceptre = importlib.import_module('sceptre') mock = importlib.import_module('mock') evaluated_stack = eval(repr(self.stack), { 'sceptre': sceptre, 'sentinel': mock.mock.sentinel }) assert isinstance(evaluated_stack, Stack) assert evaluated_stack.__eq__(self.stack)
class TestStack(object): @patch("sceptre.stack.Stack.config") def setup_method(self, test_method, mock_config): self.mock_environment_config = MagicMock(spec=Config) self.mock_environment_config.environment_path = sentinel.path # environment config is an object which inherits from dict. Its # attributes are accessable via dot and square bracket notation. # In order to mimic the behaviour of the square bracket notation, # a side effect is used to return the expected value from the call to # __getitem__ that the square bracket notation makes. self.mock_environment_config.__getitem__.side_effect = [ sentinel.project_code, sentinel.region ] self.mock_connection_manager = Mock() self.stack = Stack( name="stack_name", environment_config=self.mock_environment_config, connection_manager=self.mock_connection_manager ) # Set default value for stack properties self.stack._external_name = sentinel.external_name def test_initiate_stack(self): assert self.stack.name == "stack_name" assert self.stack.environment_config == self.mock_environment_config assert self.stack.project == sentinel.project_code assert self.stack._environment_path == sentinel.path assert self.stack._config is None assert self.stack._template is None assert self.stack.region == sentinel.region assert self.stack.connection_manager == self.mock_connection_manager assert self.stack._hooks is None assert self.stack._dependencies is None @patch("sceptre.stack.Stack.config") def test_initialiser_calls_correct_methods(self, mock_config): mock_config.get.return_value = sentinel.hooks self.stack._config = { "parameters": sentinel.parameters, "hooks": sentinel.hooks } self.mock_environment_config = MagicMock(spec=Config) self.mock_environment_config.environment_path = sentinel.path # environment config is an object which inherits from dict. Its # attributes are accessable via dot and square bracket notation. # In order to mimic the behaviour of the square bracket notation, # a side effect is used to return the expected value from the call to # __getitem__ that the square bracket notation makes. self.mock_environment_config.__getitem__.side_effect = [ sentinel.project_code, sentinel.template_bucket_name, sentinel.region ] Stack( name=sentinel.name, environment_config=self.mock_environment_config, connection_manager=sentinel.connection_manager ) def test_repr(self): self.stack.name = "stack_name" self.stack.environment_config = {"key": "val"} self.stack.connection_manager = "connection_manager" assert self.stack.__repr__() == \ "sceptre.stack.Stack(stack_name='stack_name', \ environment_config={'key': 'val'}, connection_manager=connection_manager)" @patch("sceptre.stack.Config") def test_config_loads_config(self, mock_Config): self.stack._config = None self.stack.name = "stack" # self.stack.environment_config = MagicMock(spec=Config) self.stack.environment_config.sceptre_dir = sentinel.sceptre_dir self.stack.environment_config.environment_path = \ sentinel.environment_path self.stack.environment_config.get.return_value = \ sentinel.user_variables mock_config = Mock() mock_Config.with_yaml_constructors.return_value = mock_config response = self.stack.config mock_Config.with_yaml_constructors.assert_called_once_with( sceptre_dir=sentinel.sceptre_dir, environment_path=sentinel.environment_path, base_file_name="stack", environment_config=self.stack.environment_config, connection_manager=self.stack.connection_manager ) mock_config.read.assert_called_once_with(sentinel.user_variables, self.stack.environment_config) assert response == mock_config def test_config_returns_config_if_it_exists(self): self.stack._config = sentinel.config response = self.stack.config assert response == sentinel.config def test_dependencies_loads_dependencies(self): self.stack.name = "dev/security-group" self.stack._config = { "dependencies": ["dev/vpc", "dev/vpc", "dev/subnets"] } dependencies = self.stack.dependencies assert dependencies == set(["dev/vpc", "dev/subnets"]) def test_dependencies_returns_dependencies_if_it_exists(self): self.stack._dependencies = sentinel.dependencies response = self.stack.dependencies assert response == sentinel.dependencies def test_hooks_with_no_cache(self): self.stack._hooks = None self.stack._config = {} self.stack._config["hooks"] = sentinel.hooks assert self.stack.hooks == sentinel.hooks def test_hooks_with_cache(self): self.stack._hooks = sentinel.hooks assert self.stack.hooks == sentinel.hooks @patch("sceptre.stack.Template") def test_template_loads_template(self, mock_Template): self.stack._template = None self.stack.environment_config.sceptre_dir = "sceptre_dir" self.stack._config = { "template_path": "template_path", "sceptre_user_data": sentinel.sceptre_user_data } mock_Template.return_value = sentinel.template response = self.stack.template mock_Template.assert_called_once_with( path="sceptre_dir/template_path", sceptre_user_data=sentinel.sceptre_user_data ) assert response == sentinel.template def test_template_returns_template_if_it_exists(self): self.stack._template = sentinel.template response = self.stack.template assert response == sentinel.template @patch("sceptre.stack.get_external_stack_name") def test_external_name_with_custom_stack_name( self, mock_get_external_stack_name ): self.stack._external_name = None self.stack._config = {"stack_name": "custom_stack_name"} external_name = self.stack.external_name assert external_name == "custom_stack_name" def test_external_name_without_custom_name(self): self.stack._external_name = None self.stack.project = "project" self.stack.name = "stack-name" self.stack._config = {} external_name = self.stack.external_name assert external_name == "project-stack-name" @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack._get_template_details") def test_create_sends_correct_request( self, mock_get_template_details, mock_wait_for_completion, mock_format_params ): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = {"stack_tags": { "tag1": "val1" }} self.stack._hooks = {} self.stack.config["role_arn"] = sentinel.role_arn self.stack.config["notifications"] = [sentinel.notification] self.stack.create() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [sentinel.notification], "Tags": [ {"Key": "tag1", "Value": "val1"} ] } ) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack._get_template_details") def test_create_sends_correct_request_no_notifications( self, mock_get_template_details, mock_wait_for_completion, mock_format_params ): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = {"stack_tags": { "tag1": "val1" }} self.stack._hooks = {} self.stack.config["role_arn"] = sentinel.role_arn self.stack.create() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [], "Tags": [ {"Key": "tag1", "Value": "val1"} ] } ) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack._get_template_details") def test_create_sends_correct_request_with_failure( self, mock_get_template_details, mock_wait_for_completion, mock_format_params ): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = {"stack_tags": { "tag1": "val1" }} self.stack._hooks = {} self.stack.config["role_arn"] = sentinel.role_arn self.stack.config["notifications"] = [sentinel.notification] self.stack.config["on_failure"] = 'DO_NOTHING' self.stack.create() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [sentinel.notification], "Tags": [ {"Key": "tag1", "Value": "val1"} ], "OnFailure": 'DO_NOTHING' } ) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack._get_template_details") def test_update_sends_correct_request( self, mock_get_template_details, mock_wait_for_completion, mock_format_params ): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = {"stack_tags": { "tag1": "val1" }} self.stack._hooks = {} self.stack.config["role_arn"] = sentinel.role_arn self.stack.config["notifications"] = [sentinel.notification] self.stack.update() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="update_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [sentinel.notification], "Tags": [ {"Key": "tag1", "Value": "val1"} ] } ) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack._get_template_details") def test_update_sends_correct_request_no_notification( self, mock_get_template_details, mock_wait_for_completion, mock_format_params ): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = {"stack_tags": { "tag1": "val1" }} self.stack._hooks = {} self.stack.config["role_arn"] = sentinel.role_arn self.stack.update() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="update_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "NotificationARNs": [], "Tags": [ {"Key": "tag1", "Value": "val1"} ] } ) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.create") @patch("sceptre.stack.Stack.get_status") def test_launch_with_stack_that_does_not_exist( self, mock_get_status, mock_create, mock_hooks ): self.stack._config = {"protect": False} mock_get_status.side_effect = StackDoesNotExistError() mock_create.return_value = sentinel.launch_response response = self.stack.launch() mock_create.assert_called_once_with() assert response == sentinel.launch_response @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.create") @patch("sceptre.stack.Stack.delete") @patch("sceptre.stack.Stack.get_status") def test_launch_with_stack_that_failed_to_create( self, mock_get_status, mock_delete, mock_create, mock_hooks ): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_FAILED" mock_create.return_value = sentinel.launch_response response = self.stack.launch() mock_delete.assert_called_once_with() mock_create.assert_called_once_with() assert response == sentinel.launch_response @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.update") @patch("sceptre.stack.Stack.get_status") def test_launch_with_complete_stack_with_updates_to_perform( self, mock_get_status, mock_update, mock_hooks ): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" mock_update.return_value = sentinel.launch_response response = self.stack.launch() mock_update.assert_called_once_with() assert response == sentinel.launch_response @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.update") @patch("sceptre.stack.Stack.get_status") def test_launch_with_complete_stack_with_no_updates_to_perform( self, mock_get_status, mock_update, mock_hooks ): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" mock_update.side_effect = ClientError( { "Error": { "Code": "NoUpdateToPerformError", "Message": "No updates are to be performed." } }, sentinel.operation ) response = self.stack.launch() mock_update.assert_called_once_with() assert response == StackStatus.COMPLETE @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.update") @patch("sceptre.stack.Stack.get_status") def test_launch_with_complete_stack_with_unknown_client_error( self, mock_get_status, mock_update, mock_hooks ): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" mock_update.side_effect = ClientError( { "Error": { "Code": "Boom!", "Message": "Boom!" } }, sentinel.operation ) with pytest.raises(ClientError): self.stack.launch() @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_launch_with_in_progress_stack(self, mock_get_status, mock_hooks): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_IN_PROGRESS" response = self.stack.launch() assert response == StackStatus.IN_PROGRESS @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_launch_with_failed_stack(self, mock_get_status, mock_hooks): self.stack._config = {"protect": False} mock_get_status.return_value = "UPDATE_FAILED" with pytest.raises(CannotUpdateFailedStackError): response = self.stack.launch() assert response == StackStatus.FAILED @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_launch_with_unknown_stack_status( self, mock_get_status, mock_hooks ): self.stack._config = {"protect": False} mock_get_status.return_value = "UNKNOWN_STATUS" with pytest.raises(UnknownStackStatusError): self.stack.launch() @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_with_created_stack( self, mock_get_status, mock_hooks, mock_wait_for_completion ): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" self.stack.config["role_arn"] = sentinel.role_arn self.stack.delete() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="delete_stack", kwargs={ "StackName": sentinel.external_name, "RoleARN": sentinel.role_arn } ) @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_when_wait_for_completion_raises_stack_does_not_exist_error( self, mock_get_status, mock_hooks, mock_wait_for_completion ): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" self.stack.config["role_arn"] = sentinel.role_arn mock_wait_for_completion.side_effect = StackDoesNotExistError() status = self.stack.delete() assert status == StackStatus.COMPLETE @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_when_wait_for_completion_raises_non_existent_client_error( self, mock_get_status, mock_hooks, mock_wait_for_completion ): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" self.stack.config["role_arn"] = sentinel.role_arn mock_wait_for_completion.side_effect = ClientError( { "Error": { "Code": "DoesNotExistException", "Message": "Stack does not exist" } }, sentinel.operation ) status = self.stack.delete() assert status == StackStatus.COMPLETE @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_when_wait_for_completion_raises_unexpected_client_error( self, mock_get_status, mock_hooks, mock_wait_for_completion ): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" self.stack.config["role_arn"] = sentinel.role_arn mock_wait_for_completion.side_effect = ClientError( { "Error": { "Code": "DoesNotExistException", "Message": "Boom" } }, sentinel.operation ) with pytest.raises(ClientError): self.stack.delete() @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_with_non_existent_stack( self, mock_get_status, mock_hooks, mock_wait_for_completion ): self.stack._config = {"protect": False} mock_get_status.side_effect = StackDoesNotExistError() status = self.stack.delete() assert status == StackStatus.COMPLETE def test_describe_stack_sends_correct_request(self): self.stack.describe() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_stacks", kwargs={"StackName": sentinel.external_name} ) def test_describe_events_sends_correct_request(self): self.stack.describe_events() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_stack_events", kwargs={"StackName": sentinel.external_name} ) def test_describe_resources_sends_correct_request(self): self.stack.connection_manager.call.return_value = { "StackResources": [ { "LogicalResourceId": sentinel.logical_resource_id, "PhysicalResourceId": sentinel.physical_resource_id, "OtherParam": sentinel.other_param } ] } response = self.stack.describe_resources() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_stack_resources", kwargs={"StackName": sentinel.external_name} ) assert response == [ { "LogicalResourceId": sentinel.logical_resource_id, "PhysicalResourceId": sentinel.physical_resource_id } ] @patch("sceptre.stack.Stack.describe") def test_describe_outputs_sends_correct_request(self, mock_describe): mock_describe.return_value = { "Stacks": [{ "Outputs": sentinel.outputs }] } response = self.stack.describe_outputs() mock_describe.assert_called_once_with() assert response == sentinel.outputs @patch("sceptre.stack.Stack.describe") def test_describe_outputs_handles_stack_with_no_outputs( self, mock_describe ): mock_describe.return_value = { "Stacks": [{}] } response = self.stack.describe_outputs() assert response == [] def test_continue_update_rollback_sends_correct_request(self): self.stack._config = { "template_path": sentinel.template_path, } self.stack.config["role_arn"] = sentinel.role_arn self.stack.continue_update_rollback() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="continue_update_rollback", kwargs={ "StackName": sentinel.external_name, "RoleARN": sentinel.role_arn } ) def test_set_stack_policy_sends_correct_request(self): self.stack.set_policy("tests/fixtures/stack_policies/unlock.json") self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="set_stack_policy", kwargs={ "StackName": sentinel.external_name, "StackPolicyBody": """{ "Statement" : [ { "Effect" : "Allow", "Action" : "Update:*", "Principal": "*", "Resource" : "*" } ] } """ } ) def test_get_stack_policy_sends_correct_request(self): self.stack.get_policy() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="get_stack_policy", kwargs={ "StackName": sentinel.external_name } ) @patch("sceptre.stack.Stack._get_template_details") def test_validate_template_sends_correct_request( self, mock_get_template_details ): mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name } self.stack.validate_template() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="validate_template", kwargs={"Template": sentinel.template} ) @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._get_template_details") def test_create_change_set_sends_correct_request( self, mock_get_template_details, mock_format_params ): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = { "stack_tags": {"tag1": "val1"} } self.stack.config["role_arn"] = sentinel.role_arn self.stack.config["notifications"] = [sentinel.notification] self.stack.create_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_change_set", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "ChangeSetName": sentinel.change_set_name, "RoleARN": sentinel.role_arn, "NotificationARNs": [sentinel.notification], "Tags": [ {"Key": "tag1", "Value": "val1"} ] } ) @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._get_template_details") def test_create_change_set_sends_correct_request_no_notifications( self, mock_get_template_details, mock_format_params ): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = { "stack_tags": {"tag1": "val1"} } self.stack.config["role_arn"] = sentinel.role_arn self.stack.create_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_change_set", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "ChangeSetName": sentinel.change_set_name, "RoleARN": sentinel.role_arn, "NotificationARNs": [], "Tags": [ {"Key": "tag1", "Value": "val1"} ] } ) def test_delete_change_set_sends_correct_request(self): self.stack.delete_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="delete_change_set", kwargs={ "ChangeSetName": sentinel.change_set_name, "StackName": sentinel.external_name } ) def test_describe_change_set_sends_correct_request(self): self.stack.describe_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_change_set", kwargs={ "ChangeSetName": sentinel.change_set_name, "StackName": sentinel.external_name } ) @patch("sceptre.stack.Stack._wait_for_completion") def test_execute_change_set_sends_correct_request( self, mock_wait_for_completion ): self.stack._config = {"protect": False} self.stack.execute_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="execute_change_set", kwargs={ "ChangeSetName": sentinel.change_set_name, "StackName": sentinel.external_name } ) mock_wait_for_completion.assert_called_once_with() def test_list_change_sets_sends_correct_request(self): self.stack.list_change_sets() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="list_change_sets", kwargs={"StackName": sentinel.external_name} ) @patch("sceptre.stack.Stack.set_policy") @patch("os.path.join") def test_lock_calls_set_stack_policy_with_policy( self, mock_join, mock_set_policy ): mock_join.return_value = "tests/fixtures/stack_policies/lock.json" self.stack.lock() mock_set_policy.assert_called_once_with( "tests/fixtures/stack_policies/lock.json" ) @patch("sceptre.stack.Stack.set_policy") @patch("os.path.join") def test_unlock_calls_set_stack_policy_with_policy( self, mock_join, mock_set_policy ): mock_join.return_value = "tests/fixtures/stack_policies/unlock.json" self.stack.unlock() mock_set_policy.assert_called_once_with( "tests/fixtures/stack_policies/unlock.json" ) def test_format_parameters_with_sting_values(self): parameters = { "key1": "value1", "key2": "value2", "key3": "value3" } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted( formatted_parameters, key=lambda x: x["ParameterKey"] ) assert sorted_formatted_parameters == [ {"ParameterKey": "key1", "ParameterValue": "value1"}, {"ParameterKey": "key2", "ParameterValue": "value2"}, {"ParameterKey": "key3", "ParameterValue": "value3"} ] def test_format_parameters_with_none_values(self): parameters = { "key1": None, "key2": None, "key3": None } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted( formatted_parameters, key=lambda x: x["ParameterKey"] ) assert sorted_formatted_parameters == [] def test_format_parameters_with_none_and_string_values(self): parameters = { "key1": "value1", "key2": None, "key3": "value3" } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted( formatted_parameters, key=lambda x: x["ParameterKey"] ) assert sorted_formatted_parameters == [ {"ParameterKey": "key1", "ParameterValue": "value1"}, {"ParameterKey": "key3", "ParameterValue": "value3"} ] def test_format_parameters_with_list_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": ["value4", "value5", "value6"], "key3": ["value7", "value8", "value9"] } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted( formatted_parameters, key=lambda x: x["ParameterKey"] ) assert sorted_formatted_parameters == [ {"ParameterKey": "key1", "ParameterValue": "value1,value2,value3"}, {"ParameterKey": "key2", "ParameterValue": "value4,value5,value6"}, {"ParameterKey": "key3", "ParameterValue": "value7,value8,value9"} ] def test_format_parameters_with_none_and_list_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": None, "key3": ["value7", "value8", "value9"] } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted( formatted_parameters, key=lambda x: x["ParameterKey"] ) assert sorted_formatted_parameters == [ {"ParameterKey": "key1", "ParameterValue": "value1,value2,value3"}, {"ParameterKey": "key3", "ParameterValue": "value7,value8,value9"} ] def test_format_parameters_with_list_and_string_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": "value4", "key3": ["value5", "value6", "value7"] } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted( formatted_parameters, key=lambda x: x["ParameterKey"] ) assert sorted_formatted_parameters == [ {"ParameterKey": "key1", "ParameterValue": "value1,value2,value3"}, {"ParameterKey": "key2", "ParameterValue": "value4"}, {"ParameterKey": "key3", "ParameterValue": "value5,value6,value7"} ] def test_format_parameters_with_none_list_and_string_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": "value4", "key3": None } formatted_parameters = self.stack._format_parameters(parameters) sorted_formatted_parameters = sorted( formatted_parameters, key=lambda x: x["ParameterKey"] ) assert sorted_formatted_parameters == [ {"ParameterKey": "key1", "ParameterValue": "value1,value2,value3"}, {"ParameterKey": "key2", "ParameterValue": "value4"}, ] @patch("sceptre.stack.Stack.describe") def test_get_status_with_created_stack(self, mock_describe): mock_describe.return_value = { "Stacks": [{"StackStatus": "CREATE_COMPLETE"}] } status = self.stack.get_status() assert status == "CREATE_COMPLETE" @patch("sceptre.stack.Stack.describe") def test_get_status_with_non_existent_stack(self, mock_describe): mock_describe.side_effect = ClientError( { "Error": { "Code": "DoesNotExistException", "Message": "Stack does not exist" } }, sentinel.operation ) with pytest.raises(StackDoesNotExistError): self.stack.get_status() @patch("sceptre.stack.Stack.describe") def test_get_status_with_unknown_clinet_error(self, mock_describe): mock_describe.side_effect = ClientError( { "Error": { "Code": "DoesNotExistException", "Message": "Boom!" } }, sentinel.operation ) with pytest.raises(ClientError): self.stack.get_status() def test_get_template_details_with_upload(self): self.stack._template = Mock(spec=Template) self.stack._template.upload_to_s3.return_value = sentinel.template_url self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } template_details = self.stack._get_template_details() self.stack._template.upload_to_s3.assert_called_once_with( self.stack.region, sentinel.template_bucket_name, sentinel.template_key_prefix, self.stack._environment_path, sentinel.external_name, self.stack.connection_manager ) assert template_details == {"TemplateURL": sentinel.template_url} def test_get_template_details_without_upload(self): self.stack._template = Mock(spec=Template) self.stack._template.body = sentinel.body self.stack.environment_config = { "template_key_prefix": sentinel.template_key_prefix } template_details = self.stack._get_template_details() assert template_details == {"TemplateBody": sentinel.body} def test_get_role_arn_without_role(self): self.stack._template = Mock(spec=Template) self.stack._config = { "template_path": sentinel.template_path, } assert self.stack._get_role_arn() == {} def test_get_role_arn_with_role(self): self.stack._template = Mock(spec=Template) self.stack._config = { "template_path": sentinel.template_path, } self.stack.config["role_arn"] = sentinel.role_arn assert self.stack._get_role_arn() == {"RoleARN": sentinel.role_arn} def test_protect_execution_without_protection(self): self.stack._config = {"protect": False} # Function should do nothing if protect == False self.stack._protect_execution() def test_protect_execution_without_explicit_protection(self): self.stack._config = {} # Function should do nothing if protect isn't explicitly set self.stack._protect_execution() def test_protect_execution_with_protection(self): self.stack._config = {"protect": True} with pytest.raises(ProtectedStackError): self.stack._protect_execution() @patch("sceptre.stack.time") @patch("sceptre.stack.Stack._log_new_events") @patch("sceptre.stack.Stack.get_status") @patch("sceptre.stack.Stack._get_simplified_status") def test_wait_for_completion_calls_log_new_events( self, mock_get_simplified_status, mock_get_status, mock_log_new_events, mock_time ): mock_get_simplified_status.return_value = StackStatus.COMPLETE self.stack._wait_for_completion() mock_log_new_events.assert_called_once_with() @pytest.mark.parametrize("test_input,expected", [ ("ROLLBACK_COMPLETE", StackStatus.FAILED), ("STACK_COMPLETE", StackStatus.COMPLETE), ("STACK_IN_PROGRESS", StackStatus.IN_PROGRESS), ("STACK_FAILED", StackStatus.FAILED) ]) def test_get_simplified_status_with_known_stack_statuses( self, test_input, expected ): response = self.stack._get_simplified_status(test_input) assert response == expected def test_get_simplified_status_with_stack_in_unknown_state(self): with pytest.raises(UnknownStackStatusError): self.stack._get_simplified_status("UNKOWN_STATUS") @patch("sceptre.stack.Stack.describe_events") def test_log_new_events_calls_describe_events(self, mock_describe_events): mock_describe_events.return_value = { "StackEvents": [] } self.stack._log_new_events() self.stack.describe_events.assert_called_once_with() @patch("sceptre.stack.Stack.describe_events") def test_log_new_events_prints_correct_event(self, mock_describe_events): mock_describe_events.return_value = { "StackEvents": [ { "Timestamp": datetime.datetime( 2016, 3, 15, 14, 2, 0, 0, tzinfo=tzutc() ), "LogicalResourceId": "id-2", "ResourceType": "type-2", "ResourceStatus": "resource-status" }, { "Timestamp": datetime.datetime( 2016, 3, 15, 14, 1, 0, 0, tzinfo=tzutc() ), "LogicalResourceId": "id-1", "ResourceType": "type-1", "ResourceStatus": "resource", "ResourceStatusReason": "User Initiated" } ] } self.stack.most_recent_event_datetime = ( datetime.datetime(2016, 3, 15, 14, 0, 0, 0, tzinfo=tzutc()) ) self.stack._log_new_events() @patch("sceptre.stack.time") @patch("sceptre.stack.Stack._get_cs_status") def test_wait_for_cs_completion_calls_get_cs_status( self, mock_get_cs_status, mock_time ): mock_get_cs_status.side_effect = [ StackChangeSetStatus.PENDING, StackChangeSetStatus.READY ] self.stack.wait_for_cs_completion(sentinel.change_set_name) mock_get_cs_status.assert_called_with(sentinel.change_set_name) @patch("sceptre.stack.Stack.describe_change_set") def test_get_cs_status_handles_all_statuses( self, mock_describe_change_set ): scss = StackChangeSetStatus return_values = { # NOQA "Status": ('CREATE_PENDING', 'CREATE_IN_PROGRESS', 'CREATE_COMPLETE', 'DELETE_COMPLETE', 'FAILED'), # NOQA "ExecutionStatus": { # NOQA 'UNAVAILABLE': (scss.PENDING, scss.PENDING, scss.PENDING, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'AVAILABLE': (scss.PENDING, scss.PENDING, scss.READY, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'EXECUTE_IN_PROGRESS': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'EXECUTE_COMPLETE': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'EXECUTE_FAILED': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'OBSOLETE': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA } # NOQA } # NOQA for i, status in enumerate(return_values['Status']): for exec_status, returns in \ return_values['ExecutionStatus'].items(): mock_describe_change_set.return_value = { "Status": status, "ExecutionStatus": exec_status } response = self.stack._get_cs_status(sentinel.change_set_name) assert response == returns[i] for status in return_values['Status']: mock_describe_change_set.return_value = { "Status": status, "ExecutionStatus": 'UNKOWN_STATUS' } with pytest.raises(UnknownStackChangeSetStatusError): self.stack._get_cs_status(sentinel.change_set_name) for exec_status in return_values['ExecutionStatus'].keys(): mock_describe_change_set.return_value = { "Status": 'UNKOWN_STATUS', "ExecutionStatus": exec_status } with pytest.raises(UnknownStackChangeSetStatusError): self.stack._get_cs_status(sentinel.change_set_name) mock_describe_change_set.return_value = { "Status": 'UNKOWN_STATUS', "ExecutionStatus": 'UNKOWN_STATUS', } with pytest.raises(UnknownStackChangeSetStatusError): self.stack._get_cs_status(sentinel.change_set_name) @patch("sceptre.stack.Stack.describe_change_set") def test_get_cs_status_raises_unexpected_exceptions( self, mock_describe_change_set ): mock_describe_change_set.side_effect = ClientError( { "Error": { "Code": "ChangeSetNotFound", "Message": "ChangeSet [*] does not exist" } }, sentinel.operation ) with pytest.raises(ClientError): self.stack._get_cs_status(sentinel.change_set_name)
class TestStack(object): def setup_method(self, test_method): self.stack = Stack( name='dev/app/stack', project_code=sentinel.project_code, template_bucket_name=sentinel.template_bucket_name, template_key_prefix=sentinel.template_key_prefix, required_version=sentinel.required_version, template_path=sentinel.template_path, region=sentinel.region, profile=sentinel.profile, parameters={"key1": "val1"}, sceptre_user_data=sentinel.sceptre_user_data, hooks={}, s3_details=None, dependencies=sentinel.dependencies, role_arn=sentinel.role_arn, protected=False, tags={"tag1": "val1"}, external_name=sentinel.external_name, notifications=[sentinel.notification], on_failure=sentinel.on_failure, iam_role=sentinel.iam_role, iam_role_session_duration=sentinel.iam_role_session_duration, stack_timeout=sentinel.stack_timeout, stack_group_config={}) self.stack._template = MagicMock(spec=Template) def test_initiate_stack_with_template_path(self): stack = Stack(name='dev/stack/app', project_code=sentinel.project_code, template_path=sentinel.template_path, template_bucket_name=sentinel.template_bucket_name, template_key_prefix=sentinel.template_key_prefix, required_version=sentinel.required_version, region=sentinel.region, external_name=sentinel.external_name) assert stack.name == 'dev/stack/app' assert stack.project_code == sentinel.project_code assert stack.template_bucket_name == sentinel.template_bucket_name assert stack.template_key_prefix == sentinel.template_key_prefix assert stack.required_version == sentinel.required_version assert stack.external_name == sentinel.external_name assert stack.hooks == {} assert stack.parameters == {} assert stack.sceptre_user_data == {} assert stack.template_path == sentinel.template_path assert stack.template_handler_config is None assert stack.s3_details is None assert stack._template is None assert stack.protected is False assert stack.iam_role is None assert stack.role_arn is None assert stack.dependencies == [] assert stack.tags == {} assert stack.notifications == [] assert stack.on_failure is None assert stack.stack_group_config == {} def test_initiate_stack_with_template_handler(self): stack = Stack(name='dev/stack/app', project_code=sentinel.project_code, template_handler_config=sentinel.template_handler_config, template_bucket_name=sentinel.template_bucket_name, template_key_prefix=sentinel.template_key_prefix, required_version=sentinel.required_version, region=sentinel.region, external_name=sentinel.external_name) assert stack.name == 'dev/stack/app' assert stack.project_code == sentinel.project_code assert stack.template_bucket_name == sentinel.template_bucket_name assert stack.template_key_prefix == sentinel.template_key_prefix assert stack.required_version == sentinel.required_version assert stack.external_name == sentinel.external_name assert stack.hooks == {} assert stack.parameters == {} assert stack.sceptre_user_data == {} assert stack.template_path is None assert stack.template_handler_config == sentinel.template_handler_config assert stack.s3_details is None assert stack._template is None assert stack.protected is False assert stack.iam_role is None assert stack.role_arn is None assert stack.dependencies == [] assert stack.tags == {} assert stack.notifications == [] assert stack.on_failure is None assert stack.stack_group_config == {} def test_raises_exception_if_path_and_handler_configured(self): with pytest.raises(InvalidConfigFileError): Stack(name="stack_name", project_code="project_code", template_path="template_path", template_handler_config={"type": "file"}, region="region") def test_stack_repr(self): assert self.stack.__repr__() == \ "sceptre.stack.Stack(" \ "name='dev/app/stack', " \ "project_code=sentinel.project_code, " \ "template_path=sentinel.template_path, " \ "template_handler_config=None, " \ "region=sentinel.region, " \ "template_bucket_name=sentinel.template_bucket_name, "\ "template_key_prefix=sentinel.template_key_prefix, "\ "required_version=sentinel.required_version, "\ "iam_role=sentinel.iam_role, "\ "iam_role_session_duration=sentinel.iam_role_session_duration, "\ "profile=sentinel.profile, " \ "sceptre_user_data=sentinel.sceptre_user_data, " \ "parameters={'key1': 'val1'}, "\ "hooks={}, "\ "s3_details=None, " \ "dependencies=sentinel.dependencies, "\ "role_arn=sentinel.role_arn, "\ "protected=False, "\ "tags={'tag1': 'val1'}, "\ "external_name=sentinel.external_name, " \ "notifications=[sentinel.notification], " \ "on_failure=sentinel.on_failure, " \ "stack_timeout=sentinel.stack_timeout, " \ "stack_group_config={}" \ ")" def test_repr_can_eval_correctly(self): sceptre = importlib.import_module('sceptre') evaluated_stack = eval(repr(self.stack), { 'sceptre': sceptre, 'sentinel': sentinel }) assert isinstance(evaluated_stack, Stack) assert evaluated_stack.__eq__(self.stack) def test_configuration_manager__iam_role_raises_recursive_resolve__returns_connection_manager_with_no_role( self): class FakeResolver(Resolver): def resolve(self): return self.stack.iam_role self.stack.iam_role = FakeResolver() connection_manager = self.stack.connection_manager assert connection_manager.iam_role is None def test_configuration_manager__iam_role_returns_value_second_access__returns_value_on_second_access( self): class FakeResolver(Resolver): access_count = 0 def resolve(self): if self.access_count == 0: self.access_count += 1 return self.stack.iam_role else: return 'role' self.stack.iam_role = FakeResolver() assert self.stack.connection_manager.iam_role is None assert self.stack.connection_manager.iam_role == 'role' def test_configuration_manager__iam_role_returns_value__returns_connection_manager_with_that_role( self): class FakeResolver(Resolver): def resolve(self): return 'role' self.stack.iam_role = FakeResolver() connection_manager = self.stack.connection_manager assert connection_manager.iam_role == 'role'
class TestStack(object): @patch("sceptre.stack.Stack.config") def setup_method(self, test_method, mock_config): self.mock_environment_config = MagicMock(spec=Config) self.mock_environment_config.environment_path = sentinel.path # environment config is an object which inherits from dict. Its # attributes are accessable via dot and square bracket notation. # In order to mimic the behaviour of the square bracket notation, # a side effect is used to return the expected value from the call to # __getitem__ that the square bracket notation makes. self.mock_environment_config.__getitem__.side_effect = [ sentinel.project_code, sentinel.region ] self.mock_connection_manager = Mock() self.stack = Stack(name="stack_name", environment_config=self.mock_environment_config, connection_manager=self.mock_connection_manager) # Set default value for stack properties self.stack._external_name = sentinel.external_name def test_initiate_stack(self): assert self.stack.name == "stack_name" assert self.stack.environment_config == self.mock_environment_config assert self.stack.project == sentinel.project_code assert self.stack._environment_path == sentinel.path assert self.stack._config is None assert self.stack._template is None assert self.stack.region == sentinel.region assert self.stack.connection_manager == self.mock_connection_manager assert self.stack._hooks is None assert self.stack._dependencies is None @patch("sceptre.stack.Stack.config") def test_initialiser_calls_correct_methods(self, mock_config): mock_config.get.return_value = sentinel.hooks self.stack._config = { "parameters": sentinel.parameters, "hooks": sentinel.hooks } self.mock_environment_config = MagicMock(spec=Config) self.mock_environment_config.environment_path = sentinel.path # environment config is an object which inherits from dict. Its # attributes are accessable via dot and square bracket notation. # In order to mimic the behaviour of the square bracket notation, # a side effect is used to return the expected value from the call to # __getitem__ that the square bracket notation makes. self.mock_environment_config.__getitem__.side_effect = [ sentinel.project_code, sentinel.template_bucket_name, sentinel.region ] Stack(name=sentinel.name, environment_config=self.mock_environment_config, connection_manager=sentinel.connection_manager) def test_repr(self): self.stack.name = "stack_name" self.stack.environment_config = {"key": "val"} self.stack.connection_manager = "connection_manager" assert self.stack.__repr__() == \ "sceptre.stack.Stack(stack_name='stack_name', \ environment_config={'key': 'val'}, connection_manager=connection_manager)" @patch("sceptre.stack.Config") def test_config_loads_config(self, mock_Config): self.stack._config = None self.stack.name = "stack" # self.stack.environment_config = MagicMock(spec=Config) self.stack.environment_config.sceptre_dir = sentinel.sceptre_dir self.stack.environment_config.environment_path = \ sentinel.environment_path self.stack.environment_config.get.return_value = \ sentinel.user_variables mock_config = Mock() mock_Config.with_yaml_constructors.return_value = mock_config response = self.stack.config mock_Config.with_yaml_constructors.assert_called_once_with( sceptre_dir=sentinel.sceptre_dir, environment_path=sentinel.environment_path, base_file_name="stack", environment_config=self.stack.environment_config, connection_manager=self.stack.connection_manager) mock_config.read.assert_called_once_with(sentinel.user_variables, self.stack.environment_config) assert response == mock_config def test_config_returns_config_if_it_exists(self): self.stack._config = sentinel.config response = self.stack.config assert response == sentinel.config def test_dependencies_loads_dependencies(self): self.stack.name = "dev/security-group" self.stack._config = { "dependencies": ["dev/vpc", "dev/vpc", "dev/subnets"] } dependencies = self.stack.dependencies assert dependencies == set(["dev/vpc", "dev/subnets"]) def test_dependencies_returns_dependencies_if_it_exists(self): self.stack._dependencies = sentinel.dependencies response = self.stack.dependencies assert response == sentinel.dependencies def test_hooks_with_no_cache(self): self.stack._hooks = None self.stack._config = {} self.stack._config["hooks"] = sentinel.hooks assert self.stack.hooks == sentinel.hooks def test_hooks_with_cache(self): self.stack._hooks = sentinel.hooks assert self.stack.hooks == sentinel.hooks @patch("sceptre.stack.Template") def test_template_loads_template(self, mock_Template): self.stack._template = None self.stack.environment_config.sceptre_dir = "sceptre_dir" self.stack._config = { "template_path": "template_path", "sceptre_user_data": sentinel.sceptre_user_data } mock_Template.return_value = sentinel.template response = self.stack.template mock_Template.assert_called_once_with( path="sceptre_dir/template_path", sceptre_user_data=sentinel.sceptre_user_data) assert response == sentinel.template def test_template_returns_template_if_it_exists(self): self.stack._template = sentinel.template response = self.stack.template assert response == sentinel.template @patch("sceptre.stack.get_external_stack_name") def test_external_name_with_custom_stack_name( self, mock_get_external_stack_name): self.stack._external_name = None self.stack._config = {"stack_name": "custom_stack_name"} external_name = self.stack.external_name assert external_name == "custom_stack_name" def test_external_name_without_custom_name(self): self.stack._external_name = None self.stack.project = "project" self.stack.name = "stack-name" self.stack._config = {} external_name = self.stack.external_name assert external_name == "project-stack-name" @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack._get_template_details") def test_create_sends_correct_request(self, mock_get_template_details, mock_wait_for_completion, mock_format_params): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = {"stack_tags": {"tag1": "val1"}} self.stack._hooks = {} self.stack.config["role_arn"] = sentinel.role_arn self.stack.create() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "Tags": [{ "Key": "tag1", "Value": "val1" }] }) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack._get_template_details") def test_create_sends_correct_request_with_failure( self, mock_get_template_details, mock_wait_for_completion, mock_format_params): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = {"stack_tags": {"tag1": "val1"}} self.stack._hooks = {} self.stack.config["role_arn"] = sentinel.role_arn self.stack.config["on_failure"] = 'DO_NOTHING' self.stack.create() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "Tags": [{ "Key": "tag1", "Value": "val1" }], "OnFailure": 'DO_NOTHING' }) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack._get_template_details") def test_update_sends_correct_request(self, mock_get_template_details, mock_wait_for_completion, mock_format_params): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = {"stack_tags": {"tag1": "val1"}} self.stack._hooks = {} self.stack.config["role_arn"] = sentinel.role_arn self.stack.update() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="update_stack", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "RoleARN": sentinel.role_arn, "Tags": [{ "Key": "tag1", "Value": "val1" }] }) mock_wait_for_completion.assert_called_once_with() @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.create") @patch("sceptre.stack.Stack.get_status") def test_launch_with_stack_that_does_not_exist(self, mock_get_status, mock_create, mock_hooks): self.stack._config = {"protect": False} mock_get_status.side_effect = StackDoesNotExistError() mock_create.return_value = sentinel.launch_response response = self.stack.launch() mock_create.assert_called_once_with() assert response == sentinel.launch_response @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.create") @patch("sceptre.stack.Stack.delete") @patch("sceptre.stack.Stack.get_status") def test_launch_with_stack_that_failed_to_create(self, mock_get_status, mock_delete, mock_create, mock_hooks): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_FAILED" mock_create.return_value = sentinel.launch_response response = self.stack.launch() mock_delete.assert_called_once_with() mock_create.assert_called_once_with() assert response == sentinel.launch_response @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.update") @patch("sceptre.stack.Stack.get_status") def test_launch_with_complete_stack_with_updates_to_perform( self, mock_get_status, mock_update, mock_hooks): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" mock_update.return_value = sentinel.launch_response response = self.stack.launch() mock_update.assert_called_once_with() assert response == sentinel.launch_response @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.update") @patch("sceptre.stack.Stack.get_status") def test_launch_with_complete_stack_with_no_updates_to_perform( self, mock_get_status, mock_update, mock_hooks): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" mock_update.side_effect = ClientError( { "Error": { "Code": "NoUpdateToPerformError", "Message": "No updates are to be performed." } }, sentinel.operation) response = self.stack.launch() mock_update.assert_called_once_with() assert response == StackStatus.COMPLETE @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.update") @patch("sceptre.stack.Stack.get_status") def test_launch_with_complete_stack_with_unknown_client_error( self, mock_get_status, mock_update, mock_hooks): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" mock_update.side_effect = ClientError( {"Error": { "Code": "Boom!", "Message": "Boom!" }}, sentinel.operation) with pytest.raises(ClientError): self.stack.launch() @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_launch_with_in_progress_stack(self, mock_get_status, mock_hooks): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_IN_PROGRESS" response = self.stack.launch() assert response == StackStatus.IN_PROGRESS @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_launch_with_failed_stack(self, mock_get_status, mock_hooks): self.stack._config = {"protect": False} mock_get_status.return_value = "UPDATE_FAILED" with pytest.raises(CannotUpdateFailedStackError): response = self.stack.launch() assert response == StackStatus.FAILED @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_launch_with_unknown_stack_status(self, mock_get_status, mock_hooks): self.stack._config = {"protect": False} mock_get_status.return_value = "UNKNOWN_STATUS" with pytest.raises(UnknownStackStatusError): self.stack.launch() @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_with_created_stack(self, mock_get_status, mock_hooks, mock_wait_for_completion): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" self.stack.config["role_arn"] = sentinel.role_arn self.stack.delete() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="delete_stack", kwargs={ "StackName": sentinel.external_name, "RoleARN": sentinel.role_arn }) @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_when_wait_for_completion_raises_stack_does_not_exist_error( self, mock_get_status, mock_hooks, mock_wait_for_completion): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" self.stack.config["role_arn"] = sentinel.role_arn mock_wait_for_completion.side_effect = StackDoesNotExistError() status = self.stack.delete() assert status == StackStatus.COMPLETE @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_when_wait_for_completion_raises_non_existent_client_error( self, mock_get_status, mock_hooks, mock_wait_for_completion): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" self.stack.config["role_arn"] = sentinel.role_arn mock_wait_for_completion.side_effect = ClientError( { "Error": { "Code": "DoesNotExistException", "Message": "Stack does not exist" } }, sentinel.operation) status = self.stack.delete() assert status == StackStatus.COMPLETE @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_when_wait_for_completion_raises_unexpected_client_error( self, mock_get_status, mock_hooks, mock_wait_for_completion): self.stack._config = {"protect": False} mock_get_status.return_value = "CREATE_COMPLETE" self.stack.config["role_arn"] = sentinel.role_arn mock_wait_for_completion.side_effect = ClientError( {"Error": { "Code": "DoesNotExistException", "Message": "Boom" }}, sentinel.operation) with pytest.raises(ClientError): self.stack.delete() @patch("sceptre.stack.Stack._wait_for_completion") @patch("sceptre.stack.Stack.hooks") @patch("sceptre.stack.Stack.get_status") def test_delete_with_non_existent_stack(self, mock_get_status, mock_hooks, mock_wait_for_completion): self.stack._config = {"protect": False} mock_get_status.side_effect = StackDoesNotExistError() status = self.stack.delete() assert status == StackStatus.COMPLETE def test_describe_stack_sends_correct_request(self): self.stack.describe() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_stacks", kwargs={"StackName": sentinel.external_name}) def test_describe_events_sends_correct_request(self): self.stack.describe_events() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_stack_events", kwargs={"StackName": sentinel.external_name}) def test_describe_resources_sends_correct_request(self): self.stack.connection_manager.call.return_value = { "StackResources": [{ "LogicalResourceId": sentinel.logical_resource_id, "PhysicalResourceId": sentinel.physical_resource_id, "OtherParam": sentinel.other_param }] } response = self.stack.describe_resources() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_stack_resources", kwargs={"StackName": sentinel.external_name}) assert response == [{ "LogicalResourceId": sentinel.logical_resource_id, "PhysicalResourceId": sentinel.physical_resource_id }] @patch("sceptre.stack.Stack.describe") def test_describe_outputs_sends_correct_request(self, mock_describe): mock_describe.return_value = { "Stacks": [{ "Outputs": sentinel.outputs }] } response = self.stack.describe_outputs() mock_describe.assert_called_once_with() assert response == sentinel.outputs @patch("sceptre.stack.Stack.describe") def test_describe_outputs_handles_stack_with_no_outputs( self, mock_describe): mock_describe.return_value = {"Stacks": [{}]} response = self.stack.describe_outputs() assert response == [] @pytest.mark.parametrize( "local_template,remote_template,diff_remote_local,diff_local_remote", [ ( "local_template_content", "remote_template_content", "--- remote_template\n+++ local_template\n@@ -1 +1 @@\n-remote_template_content\n+local_template_content\n", # NOQA "--- remote_template\n+++ local_template\n@@ -1 +1 @@\n-local_template_content\n+remote_template_content\n" # NOQA ), ( "template_content\nonlylocal_content", "template_content", "--- remote_template\n+++ local_template\n@@ -1 +1,2 @@\n template_content\n+onlylocal_content\n", # NOQA "--- remote_template\n+++ local_template\n@@ -1,2 +1 @@\n template_content\n-onlylocal_content\n" # NOQA ), ( "onlylocal_content\ntemplate_content", "template_content", "--- remote_template\n+++ local_template\n@@ -1 +1,2 @@\n+onlylocal_content\n template_content\n", # NOQA "--- remote_template\n+++ local_template\n@@ -1,2 +1 @@\n-onlylocal_content\n template_content\n" # NOQA ), ( "template_content1\nonlylocal_content\ntemplate_content2", "template_content1\ntemplate_content2", "--- remote_template\n+++ local_template\n@@ -1,2 +1,3 @@\n template_content1\n+onlylocal_content\n template_content2\n", # NOQA "--- remote_template\n+++ local_template\n@@ -1,3 +1,2 @@\n template_content1\n-onlylocal_content\n template_content2\n" # NOQA ), ( "template_content1\nonlylocal_content\ntemplate_content2", "template_content1\nonlyremote_content\ntemplate_content2", "--- remote_template\n+++ local_template\n@@ -1,3 +1,3 @@\n template_content1\n-onlyremote_content\n+onlylocal_content\n template_content2\n", # NOQA "--- remote_template\n+++ local_template\n@@ -1,3 +1,3 @@\n template_content1\n-onlylocal_content\n+onlyremote_content\n template_content2\n" # NOQA ), ]) def test_diff_stack_cases(self, local_template, remote_template, diff_remote_local, diff_local_remote): self.stack._template = Mock(spec=Template) self.stack._template.body = local_template self.stack.connection_manager.call.return_value = { "TemplateBody": remote_template } response = self.stack.diff() assert response == diff_remote_local self.stack._template.body = remote_template self.stack.connection_manager.call.return_value = { "TemplateBody": local_template } response = self.stack.diff() assert response == diff_local_remote def test_continue_update_rollback_sends_correct_request(self): self.stack._config = { "template_path": sentinel.template_path, } self.stack.config["role_arn"] = sentinel.role_arn self.stack.continue_update_rollback() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="continue_update_rollback", kwargs={ "StackName": sentinel.external_name, "RoleARN": sentinel.role_arn }) def test_set_stack_policy_sends_correct_request(self): self.stack.set_policy("tests/fixtures/stack_policies/unlock.json") self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="set_stack_policy", kwargs={ "StackName": sentinel.external_name, "StackPolicyBody": """{ "Statement" : [ { "Effect" : "Allow", "Action" : "Update:*", "Principal": "*", "Resource" : "*" } ] } """ }) def test_get_stack_policy_sends_correct_request(self): self.stack.get_policy() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="get_stack_policy", kwargs={"StackName": sentinel.external_name}) @patch("sceptre.stack.Stack._get_template_details") def test_validate_template_sends_correct_request( self, mock_get_template_details): mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name } self.stack.validate_template() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="validate_template", kwargs={"Template": sentinel.template}) @patch("sceptre.stack.Stack._format_parameters") @patch("sceptre.stack.Stack._get_template_details") def test_create_change_set_sends_correct_request(self, mock_get_template_details, mock_format_params): mock_format_params.return_value = sentinel.parameters mock_get_template_details.return_value = { "Template": sentinel.template } self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } self.stack._config = {"stack_tags": {"tag1": "val1"}} self.stack.config["role_arn"] = sentinel.role_arn self.stack.create_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="create_change_set", kwargs={ "StackName": sentinel.external_name, "Template": sentinel.template, "Parameters": sentinel.parameters, "Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], "ChangeSetName": sentinel.change_set_name, "RoleARN": sentinel.role_arn, "Tags": [{ "Key": "tag1", "Value": "val1" }] }) def test_delete_change_set_sends_correct_request(self): self.stack.delete_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="delete_change_set", kwargs={ "ChangeSetName": sentinel.change_set_name, "StackName": sentinel.external_name }) def test_describe_change_set_sends_correct_request(self): self.stack.describe_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="describe_change_set", kwargs={ "ChangeSetName": sentinel.change_set_name, "StackName": sentinel.external_name }) @patch("sceptre.stack.Stack._wait_for_completion") def test_execute_change_set_sends_correct_request( self, mock_wait_for_completion): self.stack._config = {"protect": False} self.stack.execute_change_set(sentinel.change_set_name) self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="execute_change_set", kwargs={ "ChangeSetName": sentinel.change_set_name, "StackName": sentinel.external_name }) mock_wait_for_completion.assert_called_once_with() def test_list_change_sets_sends_correct_request(self): self.stack.list_change_sets() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="list_change_sets", kwargs={"StackName": sentinel.external_name}) def test_diff_stack_sends_correct_request(self): self.stack._template = Mock(spec=Template) self.stack._template.body = "local_template_content" self.stack.connection_manager.call.return_value = \ {"TemplateBody": "remote_template_content"} self.stack.diff() self.stack.connection_manager.call.assert_called_with( service="cloudformation", command="get_template", kwargs={"StackName": sentinel.external_name}) @patch("sceptre.stack.Stack.set_policy") @patch("os.path.join") def test_lock_calls_set_stack_policy_with_policy(self, mock_join, mock_set_policy): mock_join.return_value = "tests/fixtures/stack_policies/lock.json" self.stack.lock() mock_set_policy.assert_called_once_with( "tests/fixtures/stack_policies/lock.json") @patch("sceptre.stack.Stack.set_policy") @patch("os.path.join") def test_unlock_calls_set_stack_policy_with_policy(self, mock_join, mock_set_policy): mock_join.return_value = "tests/fixtures/stack_policies/unlock.json" self.stack.unlock() mock_set_policy.assert_called_once_with( "tests/fixtures/stack_policies/unlock.json") def test_format_parameters_with_sting_values(self): parameters = {"key1": "value1", "key2": "value2", "key3": "value3"} formatted_parameters = self.stack._format_parameters(parameters) assert sorted(formatted_parameters) == sorted([{ "ParameterKey": "key1", "ParameterValue": "value1" }, { "ParameterKey": "key2", "ParameterValue": "value2" }, { "ParameterKey": "key3", "ParameterValue": "value3" }]) def test_format_parameters_with_none_values(self): parameters = {"key1": None, "key2": None, "key3": None} formatted_parameters = self.stack._format_parameters(parameters) assert sorted(formatted_parameters) == [] def test_format_parameters_with_none_and_string_values(self): parameters = {"key1": "value1", "key2": None, "key3": "value3"} formatted_parameters = self.stack._format_parameters(parameters) assert sorted(formatted_parameters) == sorted([{ "ParameterKey": "key1", "ParameterValue": "value1" }, { "ParameterKey": "key3", "ParameterValue": "value3" }]) def test_format_parameters_with_list_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": ["value4", "value5", "value6"], "key3": ["value7", "value8", "value9"] } formatted_parameters = self.stack._format_parameters(parameters) assert sorted(formatted_parameters) == sorted([{ "ParameterKey": "key1", "ParameterValue": "value1,value2,value3" }, { "ParameterKey": "key2", "ParameterValue": "value4,value5,value6" }, { "ParameterKey": "key3", "ParameterValue": "value7,value8,value9" }]) def test_format_parameters_with_none_and_list_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": None, "key3": ["value7", "value8", "value9"] } formatted_parameters = self.stack._format_parameters(parameters) assert sorted(formatted_parameters) == sorted([{ "ParameterKey": "key1", "ParameterValue": "value1,value2,value3" }, { "ParameterKey": "key3", "ParameterValue": "value7,value8,value9" }]) def test_format_parameters_with_list_and_string_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": "value4", "key3": ["value5", "value6", "value7"] } formatted_parameters = self.stack._format_parameters(parameters) assert sorted(formatted_parameters) == sorted([{ "ParameterKey": "key1", "ParameterValue": "value1,value2,value3" }, { "ParameterKey": "key2", "ParameterValue": "value4" }, { "ParameterKey": "key3", "ParameterValue": "value5,value6,value7" }]) def test_format_parameters_with_none_list_and_string_values(self): parameters = { "key1": ["value1", "value2", "value3"], "key2": "value4", "key3": None } formatted_parameters = self.stack._format_parameters(parameters) assert sorted(formatted_parameters) == sorted([ { "ParameterKey": "key1", "ParameterValue": "value1,value2,value3" }, { "ParameterKey": "key2", "ParameterValue": "value4" }, ]) @patch("sceptre.stack.Stack.describe") def test_get_status_with_created_stack(self, mock_describe): mock_describe.return_value = { "Stacks": [{ "StackStatus": "CREATE_COMPLETE" }] } status = self.stack.get_status() assert status == "CREATE_COMPLETE" @patch("sceptre.stack.Stack.describe") def test_get_status_with_non_existent_stack(self, mock_describe): mock_describe.side_effect = ClientError( { "Error": { "Code": "DoesNotExistException", "Message": "Stack does not exist" } }, sentinel.operation) with pytest.raises(StackDoesNotExistError): self.stack.get_status() @patch("sceptre.stack.Stack.describe") def test_get_status_with_unknown_clinet_error(self, mock_describe): mock_describe.side_effect = ClientError( {"Error": { "Code": "DoesNotExistException", "Message": "Boom!" }}, sentinel.operation) with pytest.raises(ClientError): self.stack.get_status() def test_get_template_details_with_upload(self): self.stack._template = Mock(spec=Template) self.stack._template.upload_to_s3.return_value = sentinel.template_url self.stack.environment_config = { "template_bucket_name": sentinel.template_bucket_name, "template_key_prefix": sentinel.template_key_prefix } template_details = self.stack._get_template_details() self.stack._template.upload_to_s3.assert_called_once_with( self.stack.region, sentinel.template_bucket_name, sentinel.template_key_prefix, self.stack._environment_path, sentinel.external_name, self.stack.connection_manager) assert template_details == {"TemplateURL": sentinel.template_url} def test_get_template_details_without_upload(self): self.stack._template = Mock(spec=Template) self.stack._template.body = sentinel.body self.stack.environment_config = { "template_key_prefix": sentinel.template_key_prefix } template_details = self.stack._get_template_details() assert template_details == {"TemplateBody": sentinel.body} def test_get_role_arn_without_role(self): self.stack._template = Mock(spec=Template) self.stack._config = { "template_path": sentinel.template_path, } assert self.stack._get_role_arn() == {} def test_get_role_arn_with_role(self): self.stack._template = Mock(spec=Template) self.stack._config = { "template_path": sentinel.template_path, } self.stack.config["role_arn"] = sentinel.role_arn assert self.stack._get_role_arn() == {"RoleARN": sentinel.role_arn} def test_protect_execution_without_protection(self): self.stack._config = {"protect": False} # Function should do nothing if protect == False self.stack._protect_execution() def test_protect_execution_without_explicit_protection(self): self.stack._config = {} # Function should do nothing if protect isn't explicitly set self.stack._protect_execution() def test_protect_execution_with_protection(self): self.stack._config = {"protect": True} with pytest.raises(ProtectedStackError): self.stack._protect_execution() @patch("sceptre.stack.time") @patch("sceptre.stack.Stack._log_new_events") @patch("sceptre.stack.Stack.get_status") @patch("sceptre.stack.Stack._get_simplified_status") def test_wait_for_completion_calls_log_new_events( self, mock_get_simplified_status, mock_get_status, mock_log_new_events, mock_time): mock_get_simplified_status.return_value = StackStatus.COMPLETE self.stack._wait_for_completion() mock_log_new_events.assert_called_once_with() @pytest.mark.parametrize("test_input,expected", [("ROLLBACK_COMPLETE", StackStatus.FAILED), ("STACK_COMPLETE", StackStatus.COMPLETE), ("STACK_IN_PROGRESS", StackStatus.IN_PROGRESS), ("STACK_FAILED", StackStatus.FAILED)]) def test_get_simplified_status_with_known_stack_statuses( self, test_input, expected): response = self.stack._get_simplified_status(test_input) assert response == expected def test_get_simplified_status_with_stack_in_unknown_state(self): with pytest.raises(UnknownStackStatusError): self.stack._get_simplified_status("UNKOWN_STATUS") @patch("sceptre.stack.Stack.describe_events") def test_log_new_events_calls_describe_events(self, mock_describe_events): mock_describe_events.return_value = {"StackEvents": []} self.stack._log_new_events() self.stack.describe_events.assert_called_once_with() @patch("sceptre.stack.Stack.describe_events") def test_log_new_events_prints_correct_event(self, mock_describe_events): mock_describe_events.return_value = { "StackEvents": [{ "Timestamp": datetime.datetime(2016, 3, 15, 14, 2, 0, 0, tzinfo=tzutc()), "LogicalResourceId": "id-2", "ResourceType": "type-2", "ResourceStatus": "resource-status" }, { "Timestamp": datetime.datetime(2016, 3, 15, 14, 1, 0, 0, tzinfo=tzutc()), "LogicalResourceId": "id-1", "ResourceType": "type-1", "ResourceStatus": "resource", "ResourceStatusReason": "User Initiated" }] } self.stack.most_recent_event_datetime = (datetime.datetime( 2016, 3, 15, 14, 0, 0, 0, tzinfo=tzutc())) self.stack._log_new_events() @patch("sceptre.stack.time") @patch("sceptre.stack.Stack._get_cs_status") def test_wait_for_cs_completion_calls_get_cs_status( self, mock_get_cs_status, mock_time): mock_get_cs_status.side_effect = [ StackChangeSetStatus.PENDING, StackChangeSetStatus.READY ] self.stack.wait_for_cs_completion(sentinel.change_set_name) mock_get_cs_status.assert_called_with(sentinel.change_set_name) @patch("sceptre.stack.Stack.describe_change_set") def test_get_cs_status_handles_all_statuses(self, mock_describe_change_set): scss = StackChangeSetStatus return_values = { # NOQA "Status": ('CREATE_PENDING', 'CREATE_IN_PROGRESS', 'CREATE_COMPLETE', 'DELETE_COMPLETE', 'FAILED'), # NOQA "ExecutionStatus": { # NOQA 'UNAVAILABLE': (scss.PENDING, scss.PENDING, scss.PENDING, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'AVAILABLE': (scss.PENDING, scss.PENDING, scss.READY, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'EXECUTE_IN_PROGRESS': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'EXECUTE_COMPLETE': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'EXECUTE_FAILED': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA 'OBSOLETE': (scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT, scss.DEFUNCT), # NOQA } # NOQA } # NOQA for i, status in enumerate(return_values['Status']): for exec_status, returns in \ return_values['ExecutionStatus'].items(): mock_describe_change_set.return_value = { "Status": status, "ExecutionStatus": exec_status } response = self.stack._get_cs_status(sentinel.change_set_name) assert response == returns[i] for status in return_values['Status']: mock_describe_change_set.return_value = { "Status": status, "ExecutionStatus": 'UNKOWN_STATUS' } with pytest.raises(UnknownStackChangeSetStatusError): self.stack._get_cs_status(sentinel.change_set_name) for exec_status in return_values['ExecutionStatus'].keys(): mock_describe_change_set.return_value = { "Status": 'UNKOWN_STATUS', "ExecutionStatus": exec_status } with pytest.raises(UnknownStackChangeSetStatusError): self.stack._get_cs_status(sentinel.change_set_name) mock_describe_change_set.return_value = { "Status": 'UNKOWN_STATUS', "ExecutionStatus": 'UNKOWN_STATUS', } with pytest.raises(UnknownStackChangeSetStatusError): self.stack._get_cs_status(sentinel.change_set_name) @patch("sceptre.stack.Stack.describe_change_set") def test_get_cs_status_raises_unexpected_exceptions( self, mock_describe_change_set): mock_describe_change_set.side_effect = ClientError( { "Error": { "Code": "ChangeSetNotFound", "Message": "ChangeSet [*] does not exist" } }, sentinel.operation) with pytest.raises(ClientError): self.stack._get_cs_status(sentinel.change_set_name)