Esempio n. 1
0
    def handle_stack_event(self, event, valid_from_timestamp,
                           expected_stack_event_status, stack_name):
        """
        Handle stack event. Return it if it has the expected status
        :param event: raw event
        :param valid_from_timestamp: earliest timestamp from which the event is considered relevant
        :param expected_stack_event_status:
        :param stack_name: the relevant stacks name
        :return: boto3 stack event if it has expected status | None
        :raise CfnStackActionFailedException:
        """
        if event["Timestamp"] > valid_from_timestamp:

            if event["ResourceType"] == "AWS::CloudFormation::Stack" and event[
                    "LogicalResourceId"] == stack_name:
                self.logger.debug("Raw event: {0}".format(event))

                if event["ResourceStatus"] == expected_stack_event_status:
                    return event

                if event["ResourceStatus"].endswith("_FAILED"):
                    raise CfnStackActionFailedException(
                        "Stack is in {0} state".format(
                            event["ResourceStatus"]))

                if event["ResourceStatus"].endswith("ROLLBACK_IN_PROGRESS"):
                    self.logger.error(
                        "Failed to create stack (Reason: {0})".format(
                            event["ResourceStatusReason"]))
                    return None

                if event["ResourceStatus"].endswith("ROLLBACK_COMPLETE"):
                    raise CfnStackActionFailedException("Rollback occured")
            else:
                if event["ResourceStatus"].endswith("_FAILED"):
                    self.logger.error(
                        "Failed to create {0} (Reason: {1})".format(
                            event["LogicalResourceId"],
                            event["ResourceStatusReason"]))
                    return None
                else:
                    status_reason = event.get("ResourceStatusReason", None)
                    status_reason_string = " ({0})".format(
                        status_reason) if status_reason else ""
                    event_string = "{0} {1}: {2}{3}".format(
                        event["StackName"], event["LogicalResourceId"],
                        event["ResourceStatus"], status_reason_string)

                    self.logger.info(event_string)
                    return None
Esempio n. 2
0
    def wait_for_stack_events(self, stack_name, expected_event,
                              valid_from_timestamp, timeout):
        self.logger.debug("Waiting for {0} events, newer than {1}".format(
            expected_event, valid_from_timestamp))

        seen_event_ids = []
        start = time.time()
        while time.time() < (start + timeout):

            events = self.get_stack_events(stack_name)
            events.reverse()

            for event in events:
                if event.event_id not in seen_event_ids:
                    seen_event_ids.append(event.event_id)
                    if event.timestamp > valid_from_timestamp:

                        if event.resource_type == "AWS::CloudFormation::Stack":
                            self.logger.debug(event)

                            if event.resource_status == expected_event:
                                return event
                            if event.resource_status.endswith("_FAILED"):
                                raise CfnStackActionFailedException(
                                    "Stack is in {0} state".format(
                                        event.resource_status))
                            if event.resource_status.endswith(
                                    "ROLLBACK_IN_PROGRESS"):
                                self.logger.error(
                                    "Failed to create stack (Reason: {0})".
                                    format(event.resource_status_reason))
                            if event.resource_status.endswith(
                                    "ROLLBACK_COMPLETE"):
                                raise CfnStackActionFailedException(
                                    "Rollback occured")
                        else:
                            if event.resource_status.endswith("_FAILED"):
                                self.logger.error(
                                    "Failed to create {0} (Reason: {1})".
                                    format(event.logical_resource_id,
                                           event.resource_status_reason))
                            else:
                                self.logger.info(event)

            time.sleep(10)
        raise CfnStackActionFailedException(
            "Timeout occurred waiting for '{0}' on stack {1}".format(
                expected_event, stack_name))
Esempio n. 3
0
    def create_stack(self, stack):
        self.logger.debug("Creating stack: {0}".format(stack))

        assert isinstance(stack, CloudFormationStack)

        try:
            stack_parameters_string = get_pretty_parameters_string(stack)

            self.logger.info(
                "Creating stack {0} ({1}) with parameters:\n{2}".format(
                    stack.name, stack.template.name, stack_parameters_string))

            if self.dry_run:
                self.logger.info('Dry run. Exiting.')

                return

            self._create_stack(stack)

            self.wait_for_stack_action_to_complete(stack.name, "create",
                                                   stack.timeout)

            stack_outputs = get_pretty_stack_outputs(
                self.get_stack_outputs(stack))
            if stack_outputs:
                self.logger.info(
                    "Create completed for {0} with outputs: \n{1}".format(
                        stack.name, stack_outputs))
            else:
                self.logger.info("Create completed for {0}".format(stack.name))
        except (BotoCoreError, ClientError, CfnSphereBotoError) as e:
            raise CfnStackActionFailedException(
                "Could not create {0}: {1}".format(stack.name, e))
Esempio n. 4
0
    def update_stack(self, stack):
        try:

            try:
                self._update_stack(stack)
            except BotoServerError as e:

                if self.is_boto_no_update_required_exception(e):
                    self.logger.info(
                        "Stack {0} does not need an update".format(stack.name))
                    return
                else:
                    self.logger.info(
                        "Updating stack {0} ({1}) with parameters:\n{2}".
                        format(stack.name, stack.template.name,
                               get_pretty_parameters_string(stack.parameters)))
                    raise

            self.logger.info(
                "Updating stack {0} ({1}) with parameters:\n{2}".format(
                    stack.name, stack.template.name,
                    get_pretty_parameters_string(stack.parameters)))

            self.wait_for_stack_action_to_complete(stack.name, "update",
                                                   stack.timeout)
            self.logger.info("Update completed for {0}".format(stack.name))

        except BotoServerError as e:
            raise CfnStackActionFailedException(
                "Could not update {0}: {1}".format(stack.name, e.message))
Esempio n. 5
0
    def execute_change_set(self):
        print('executing change set')
        print(self.config.change_set)

        change_set = self.cfn.get_change_set(self.config.change_set)

        if change_set is None:
            raise CfnStackActionFailedException(
                "Could not execute change set {0}: Does not exist or is in an invalid state."
                .format(self.config.change_set))

        stack_name = self.cfn.get_stack_name_by_arn(change_set['StackId'])
        stack = self.cfn.get_stack(stack_name)

        if not self.cfn.change_set_is_executable(change_set):
            raise CfnStackActionFailedException(
                "Could not execute change set {0}: Does not exist or is in an invalid state."
                .format(self.config.change_set))
        else:
            self.cfn.execute_change_set(stack, self.config.change_set)
Esempio n. 6
0
    def create_change_set(self, stack, change_set_type):
        self.logger.debug("Creating stack changeset: {}".format(stack))
        assert isinstance(stack, CloudFormationStack)

        try:
            stack_parameters_string = get_pretty_parameters_string(stack)

            self.logger.info(
                "Creating stack changeset {0} ({1}) with parameters:\n{2}".
                format(stack.name, stack.template.name,
                       stack_parameters_string))
            self._create_stack_change_set(stack, change_set_type)
        except (BotoCoreError, ClientError, CfnSphereBotoError) as e:
            raise CfnStackActionFailedException(
                "Could not create change set {0}: {1}".format(stack.name, e))
Esempio n. 7
0
    def validate_stack_is_ready_for_action(self, stack):
        try:
            cfn_stack = self.get_stack(stack.name)
        except BotoServerError as e:
            raise CfnSphereBotoError(e)

        valid_states = [
            "CREATE_COMPLETE", "UPDATE_COMPLETE", "ROLLBACK_COMPLETE",
            "UPDATE_ROLLBACK_COMPLETE"
        ]

        if cfn_stack.stack_status not in valid_states:
            raise CfnStackActionFailedException(
                "Stack {0} is in '{1}' state.".format(cfn_stack.stack_name,
                                                      cfn_stack.stack_status))
Esempio n. 8
0
    def delete_stack(self, stack):
        try:
            self.logger.info("Deleting stack {0}".format(stack.name))
            self._delete_stack(stack)

            try:
                self.wait_for_stack_action_to_complete(stack.name, "delete",
                                                       600)
            except BotoServerError as e:
                self.logger.info(e)

            self.logger.info("Deletion completed for {0}".format(stack.name))
        except BotoServerError as e:
            raise CfnStackActionFailedException(
                "Could not delete {0}: {1}".format(stack.name, e.message))
Esempio n. 9
0
    def create_stack(self, stack):
        assert isinstance(stack, CloudFormationStack)
        try:
            self.logger.info(
                "Creating stack {0} from template {1} with parameters:\n{2}".
                format(stack.name, stack.template.name,
                       get_pretty_parameters_string(stack.parameters)))
            self._create_stack(stack)

            self.wait_for_stack_action_to_complete(stack.name, "create",
                                                   stack.timeout)
            self.logger.info("Create completed for {0}".format(stack.name))
        except BotoServerError as e:
            raise CfnStackActionFailedException(
                "Could not create {0}: {1}".format(stack.name, e.message))
Esempio n. 10
0
    def update_stack(self, stack):
        self.logger.debug("Updating stack: {0}".format(stack))
        assert isinstance(stack, CloudFormationStack)

        try:
            stack_parameters_string = get_pretty_parameters_string(stack)

            if stack.stack_policy:
                self.logger.info("Setting stack policy for stack {0}".format(
                    stack.name))
                self._set_stack_policy(stack)

            try:
                self._update_stack(stack)
            except ClientError as e:

                if self.is_boto_no_update_required_exception(e):
                    self.logger.info(
                        "Stack {0} does not need an update".format(stack.name))
                    return
                else:
                    self.logger.info(
                        "Updating stack {0} ({1}) with parameters:\n{2}".
                        format(stack.name, stack.template.name,
                               stack_parameters_string))
                    raise

            self.logger.info(
                "Updating stack {0} ({1}) with parameters:\n{2}".format(
                    stack.name, stack.template.name, stack_parameters_string))

            self.wait_for_stack_action_to_complete(stack.name, "update",
                                                   stack.timeout)

            stack_outputs = get_pretty_stack_outputs(
                self.get_stack_outputs(stack))
            if stack_outputs:
                self.logger.info(
                    "Update completed for {0} with outputs: \n{1}".format(
                        stack.name, stack_outputs))
            else:
                self.logger.info("Update completed for {0}".format(stack.name))
        except (BotoCoreError, ClientError, CfnSphereBotoError) as e:
            raise CfnStackActionFailedException(
                "Could not update {0}: {1}".format(stack.name, e))
Esempio n. 11
0
    def validate_stack_is_ready_for_action(self, stack):
        """
        Check if a stack is in a state capable for modification actions

        :param stack: cfn_sphere.aws.cfn.CloudFormationStack
        :raise CfnStackActionFailedException: if the stack is in an invalid state
        """
        cfn_stack = self.get_stack(stack.name)

        valid_states = [
            "CREATE_COMPLETE", "UPDATE_COMPLETE", "ROLLBACK_COMPLETE",
            "UPDATE_ROLLBACK_COMPLETE"
        ]

        if cfn_stack.stack_status not in valid_states:
            raise CfnStackActionFailedException(
                "Stack {0} is in '{1}' state.".format(cfn_stack.stack_name,
                                                      cfn_stack.stack_status))
Esempio n. 12
0
    def execute_change_set(self, stack, change_set):
        self.logger.debug("Executing stack changeset: {}".format(change_set))
        try:
            response = self.client.execute_change_set(ChangeSetName=change_set)
            self.wait_for_stack_action_to_complete(stack.name, "update", 120)
            stack_outputs = get_pretty_stack_outputs(
                self.get_stack_outputs(stack))
            if stack_outputs:
                self.logger.info(
                    "Update completed for {0} with outputs: \n{1}".format(
                        stack.name, stack_outputs))
            else:
                self.logger.info("Update completed for {0}".format(stack.name))

            # Blank it out instead of updating the cache as if it's needed again
            # the code will automatically populate the describe/stacks cache again.
            self.cached = {STACK_DESCRIPTIONS: None, RESOURCE_ALL_STACKS: None}

        except (BotoCoreError, ClientError, CfnSphereBotoError) as e:
            raise CfnStackActionFailedException(
                "Could not execute {0}: {1}".format(change_set, e))
Esempio n. 13
0
    def delete_stack(self, stack):
        self.logger.debug("Deleting stack: {0}".format(stack))
        assert isinstance(stack, CloudFormationStack)

        try:
            self.logger.info("Deleting stack {0}".format(stack.name))
            self._delete_stack(stack)

            try:
                self.wait_for_stack_action_to_complete(stack.name, "delete",
                                                       600)
            except CfnSphereBotoError as e:
                if self.is_boto_stack_does_not_exist_exception(
                        e.boto_exception):
                    pass
                else:
                    raise

            self.logger.info("Deletion completed for {0}".format(stack.name))
        except (BotoCoreError, ClientError, CfnSphereBotoError) as e:
            raise CfnStackActionFailedException(
                "Could not delete {0}: {1}".format(stack.name, e))
Esempio n. 14
0
    def wait_for_stack_event(self, stack_name, expected_event_status,
                             valid_from_timestamp, timeout):
        """
        Wait for a new stack event. Return it if it has the expected status
        :param stack_name: str
        :param expected_event_status: str
        :param valid_from_timestamp: timestamp
        :param timeout: int
        :return: boto3 stack event
        :raise CfnStackActionFailedException:
        """
        self.logger.debug("Waiting for {0} events, newer than {1}".format(
            expected_event_status, valid_from_timestamp))

        seen_event_ids = []
        start = time.time()
        while time.time() < (start + timeout):

            events = self.get_stack_events(stack_name)
            events.reverse()

            for event in events:
                if event["EventId"] not in seen_event_ids:
                    seen_event_ids.append(event["EventId"])
                    event = self.handle_stack_event(event,
                                                    valid_from_timestamp,
                                                    expected_event_status,
                                                    stack_name)

                    if event:
                        return event

            time.sleep(10)
        raise CfnStackActionFailedException(
            "Timeout occurred waiting for '{0}' on stack {1}".format(
                expected_event_status, stack_name))