Beispiel #1
0
    def service_client(self, region=None, method_names=None):
        """
        Returns a client for the service using the session/role/region of the service class instance
        :param region:
        :param method_names: names of function to create wrapper methods for with retry logic
        :return: client for making the call to describe the resources
        """

        if region is None:
            region = boto3.client(self.service_name).meta.config.region_name

        if self._service_client is None or self._service_client.meta.config.region_name != region:
            args = {
                "service_name": self.service_name,
                "region_name": region
            }

            used_session = self._session if self._session is not None else services.get_session(self.role_arn)
            self._service_client = used_session.client(**args)

        if self._service_retry_strategy is not None and method_names is not None:
            for method_name in method_names:
                if getattr(self._service_client, method_name + boto_retry.DEFAULT_SUFFIX, None) is None:
                    boto_retry.make_method_with_retries(boto_client_or_resource=self._service_client, name=method_name,
                                                        service_retry_strategy=self._service_retry_strategy)

        return self._service_client
 def service_regions(self):
     """
     Returns all regions in which a service is available
     :return:  all regions in which the service is available
     """
     return services.get_session().get_available_regions(
         service_name=self.service_name)
    def action_validate_parameters(parameters, task_settings, logger):

        valid_regions = services.get_session().get_available_regions(
            "ec2", "aws")
        region = parameters.get(PARAM_DESTINATION_REGION)
        if region not in valid_regions:
            raise_value_error(ERR_INVALID_DESTINATION_REGION, region,
                              ",".join(valid_regions))

        if parameters.get(PARAM_DELETE_AFTER_COPY, False) and parameters.get(
                PARAM_COPIED_SNAPSHOTS) != COPIED_OWNED_BY_ACCOUNT:
            raise_value_error(ERR_CANNOT_DELETE_SHARED_SNAPSHOTS)

        kms_key_id = parameters.get(PARAM_KMS_KEY_ID, None)
        if not parameters[PARAM_ENCRYPTED] and kms_key_id not in ["", None]:
            raise_value_error(ERR_KMS_KEY_ONLY_IF_ENCRYPTED, PARAM_KMS_KEY_ID)

        if kms_key_id not in ["", None]:
            if re.match(KMS_KEY_ID_PATTERN, kms_key_id) is None:
                raise_value_error(ERR_INVALID_KMS_ID_ARN, kms_key_id)

            destination_region = parameters[PARAM_DESTINATION_REGION]
            if kms_key_id.split(":")[3] != destination_region:
                raise_value_error(ERR_KMS_KEY_NOT_IN_REGION, kms_key_id,
                                  destination_region)

        return parameters
Beispiel #4
0
 def session(self):
     """
     Returns a (cached) session for the service class instance, use role if an arf for that role was provided, otherwise the
     default boto3 session is used
     :return: Session
     """
     if self._session is None:
         self._session = services.get_session(role_arn=self.role_arn,
                                              sts_client=self.sts_client if self.role_arn not in [None, ""] else None)
     return self._session
def get_task_session(account, task, this_account=False, logger=None):
    log_to_debug(logger, "Getting session for account \"{}\", task is \"{}\"", account, task[TASK_NAME])

    role_arn = get_account_role(account, task, logger=logger)

    try:
        return services.get_session(role_arn=role_arn, logger=logger), role_arn
    except Exception as ex:
        if logger is not None:
            logger.error(ERR_CREATING_SESSION, ex)
        return None, role_arn
    def __init__(self, event, context):
        """
        Initializes handler.
        :param event: Event to handle
        :param context: Context if run within Lambda environment
        """
        self._context = context
        self._event = event

        self.action_id = self._event[handlers.TASK_TR_ID]
        self.task = self._event[handlers.TASK_TR_NAME]
        self.task_timezone = self._event.get(handlers.TASK_TR_TIMEZONE, None)
        self.has_completion = self._event[handlers.TASK_TR_HAS_COMPLETION]
        self.action_parameters = self._event.get(handlers.TASK_TR_PARAMETERS,
                                                 {})
        self.dryrun = self._event.get(handlers.TASK_TR_DRYRUN)
        self.interval = self._event.get(handlers.TASK_TR_INTERVAL, None)
        self.metrics = self._event.get(handlers.TASK_TR_METRICS, False)
        self.debug = self._event.get(handlers.TASK_TR_DEBUG)
        self.started_at = int(self._event.get(handlers.TASK_TR_STARTED_TS, 0))
        self.start_result = self._event.get(handlers.TASK_TR_START_RESULT,
                                            None)
        self.session = services.get_session(
            self._event.get(handlers.TASK_TR_ASSUMED_ROLE))
        self.stack_name = os.getenv(handlers.ENV_STACK_NAME)
        self.stack_id = os.getenv(handlers.ENV_STACK_ID)
        self.action = event[handlers.TASK_TR_ACTION]
        self.tagfilter = event.get(handlers.TASK_TR_TAGFILTER, "")
        self.action_properties = actions.get_action_properties(self.action)
        self.action_class = actions.get_action_class(self.action)
        self._stack_resources = None
        self.timeout = int(
            self._event[handlers.TASK_TR_TIMEOUT]) * 60 if self._event.get(
                handlers.TASK_TR_TIMEOUT, None) not in [None, "None"] else 0
        self.execution_log_stream = self._event.get(
            handlers.TASK_TR_EXECUTION_LOGSTREAM)
        self.assumed_role = self._event.get(handlers.TASK_TR_ASSUMED_ROLE,
                                            None)
        self.events = self._event.get(handlers.TASK_TR_EVENTS, {})
        if isinstance(self.events, str):
            self.events = json.loads(
                self._event.get(handlers.TASK_TR_EVENTS,
                                "{}").replace("u'", '"').replace("'", '"'))

        self._action_resources = None
        self._s3_client = None
        self._action_instance = None
        self._action_class = None
        self._action_arguments = None
        self._timer = None
        self._timeout_event = None

        self.__logger = None
        self.__action_tracking = None
Beispiel #7
0
    def concurrency_table(self):
        """
        Returns table to store last execution time for this handler.
        :return: table to store last execution time for this handler
        """
        if self._concurrency_table is None:
            tablename = os.getenv(handlers.ENV_CONCURRENCY_TABLE)

            self._logger.debug("Using concurrency table {}", tablename)

            self._concurrency_table = services.get_session().resource(
                "dynamodb").Table(tablename)
            boto_retry.add_retry_methods_to_resource(
                self._concurrency_table,
                ["update_item", "get_item", "delete_item"],
                context=self._context)

        return self._concurrency_table
    def describe(self, as_tuple=None, **kwargs):
        """
        This method is to retrieve a pseudo UTC time resource, method parameters are only used signature compatibility
        :param as_tuple: Set to true to return results as immutable named dictionaries instead of dictionaries
        :return: Pseudo time resource
        """
        def use_tuple():
            return (as_tuple is not None and as_tuple) or (as_tuple is None
                                                           and self._as_tuple)

        region = kwargs.get("region")
        result = {
            "Time": datetime.datetime.now(pytz.timezone("UTC")),
            "AwsAccount": self.aws_account,
            "Region": region if region else services.get_session().region_name
        }

        return [as_namedtuple("Time", result)] if use_tuple() else [result]
Beispiel #9
0
def get_client_with_retries(service_name,
                            methods,
                            context=None,
                            region=None,
                            session=None,
                            wait_strategy=None,
                            method_suffix=DEFAULT_SUFFIX,
                            logger=None):
    args = {
        "service_name": service_name,
    }

    if region is not None:
        args["region_name"] = region

    user_agent = os.getenv(ENV_USER_AGENT, None)
    if user_agent is not None:
        session_config = botocore.config.Config(user_agent=user_agent)
        args["config"] = session_config

    if session is not None:
        aws_session = session
    else:
        aws_session = services.get_session()

    result = aws_session.client(**args)

    # get strategy for the service
    service_retry_strategy = get_default_retry_strategy(
        context=context,
        service=service_name,
        wait_strategy=wait_strategy,
        logger=logger)

    # add a new method to the client instance that wraps the original method with service specific retry logic
    for method in methods:
        make_method_with_retries(boto_client_or_resource=result,
                                 name=method,
                                 service_retry_strategy=service_retry_strategy,
                                 method_suffix=method_suffix)
    return result
def build_events_forward_template(template_filename, script_filename, stack,
                                  event_role_arn, ops_automator_topic_arn,
                                  version):
    with open(script_filename, "rt") as f:
        script_text = f.readlines()

    with open(template_filename, "rt") as f:
        template = json.loads("".join(f.readlines()),
                              object_pairs_hook=OrderedDict)

        code = template["Resources"]["EventsForwardFunction"]["Properties"][
            "Code"]
        code["ZipFile"]["Fn::Join"][1] = script_text

    return json.dumps(template, indent=3) \
        .replace("%version%", version) \
        .replace("%ops-automator-stack%", stack) \
        .replace("%ops-automator-region%", services.get_session().region_name) \
        .replace("%ops-automator-account%", services.get_aws_account()) \
        .replace("%ops-automator-topic-arn%", ops_automator_topic_arn) \
        .replace("%event-forward-role%", event_role_arn)
    def get_action_session(self, account, param_name=None, logger=None):
        self._logger_.debug(
            "Getting action session for account \"{}\", task is \"{}\", parameter is \"{}\"",
            account, self._task_, param_name)

        try:
            role_name = self.get(param_name, None)
            if role_name is None:
                role_name = self.get(handlers.TASK_ROLE, None)
            if role_name is None:
                if account == os.getenv(handlers.ENV_OPS_AUTOMATOR_ACCOUNT):
                    role_name = None
                else:
                    role_name = handlers.default_rolename_for_stack()

            role_arn = handlers.ARN_ROLE_TEMPLATE.format(
                account, role_name) if role_name is not None else None
            self._logger_.debug("Role arn is \"{}\"", role_arn)

            return services.get_session(role_arn=role_arn, logger=logger)
        except Exception as ex:
            if logger is not None:
                logger.error(handlers.ERR_CREATING_SESSION, ex)
            return None
class Ec2CopySnapshotAction(ActionBase):
    """
    Class implements action for copying EC2 Snapshots
    """

    properties = {
        ACTION_TITLE:
        "EC2 Copy Snapshot",
        ACTION_VERSION:
        "1.3",
        ACTION_DESCRIPTION:
        "Copies EC2 snapshot",
        ACTION_AUTHOR:
        "AWS",
        ACTION_ID:
        "eb287af5-e5c0-41cb-832b-d218c075fa26",
        ACTION_SERVICE:
        "ec2",
        ACTION_RESOURCES:
        services.ec2_service.SNAPSHOTS,
        ACTION_AGGREGATION:
        ACTION_AGGREGATION_RESOURCE,
        ACTION_COMPLETION_TIMEOUT_MINUTES:
        60,
        ACTION_MIN_INTERVAL_MIN:
        60,
        ACTION_SELECT_SIZE: [
            ACTION_SIZE_MEDIUM, ACTION_SIZE_LARGE, ACTION_SIZE_XLARGE,
            ACTION_SIZE_XXLARGE, ACTION_SIZE_XXXLARGE
        ] + [ACTION_USE_ECS],
        ACTION_EXECUTE_SIZE: [ACTION_SIZE_MEDIUM],
        ACTION_COMPLETION_SIZE: [ACTION_SIZE_MEDIUM],
        ACTION_SELECT_EXPRESSION:
        "Snapshots[?State=='completed'].{SnapshotId:SnapshotId, "
        "VolumeId:VolumeId, OwnerId:OwnerId, "
        "StartTime:StartTime,"
        "Description:Description, "
        "Tags:Tags}",
        ACTION_KEEP_RESOURCE_TAGS:
        True,
        ACTION_SELECT_PARAMETERS: {
            'RestorableByUserIds': ["self"],
        },
        ACTION_EVENTS: {
            handlers.EC2_EVENT_SOURCE: {
                handlers.ebs_snapshot_event_handler.EBS_SNAPSHOT_NOTIFICATION:
                [
                    handlers.ebs_snapshot_event_handler.EBS_SNAPSHOT_CREATED,
                    handlers.ebs_snapshot_event_handler.EBS_SNAPSHOT_SHARED
                ]
            }
        },

        # Ec2 CopySnapshot only allows 5 concurrent copies per account to a destination region
        ACTION_MAX_CONCURRENCY:
        int(
            os.getenv(handlers.ENV_SERVICE_LIMIT_CONCURRENT_EBS_SNAPSHOT_COPY,
                      5)),
        ACTION_PARAMETERS: {
            PARAM_DESTINATION_REGION: {
                PARAM_DESCRIPTION:
                PARAM_DESC_DESTINATION_REGION,
                PARAM_LABEL:
                PARAM_LABEL_DESTINATION_REGION,
                PARAM_TYPE:
                str,
                PARAM_REQUIRED:
                True,
                PARAM_DEFAULT:
                services.get_session().region_name,
                PARAM_ALLOWED_VALUES: [
                    str(r)
                    for r in services.get_session().get_available_regions(
                        "ec2", "aws")
                ]
            },
            PARAM_SNAPSHOT_DESCRIPTION: {
                PARAM_DESCRIPTION: PARAM_DESC_SNAPSHOT_DESCRIPTION,
                PARAM_LABEL: PARAM_LABEL_SNAPSHOT_DESCRIPTION,
                PARAM_TYPE: str,
                PARAM_REQUIRED: False,
            },
            PARAM_COPIED_SNAPSHOT_TAGS: {
                PARAM_DESCRIPTION: PARAM_DESC_COPIED_SNAPSHOT_TAGS,
                PARAM_LABEL: PARAM_LABEL_COPIED_SNAPSHOT_TAGS,
                PARAM_TYPE: str,
                PARAM_REQUIRED: False,
            },
            PARAM_SNAPSHOT_TAGS: {
                PARAM_DESCRIPTION: PARAM_DESC_SNAPSHOT_TAGS,
                PARAM_LABEL: PARAM_LABEL_SNAPSHOT_TAGS,
                PARAM_TYPE: str,
                PARAM_REQUIRED: False
            },
            PARAM_COPIED_SNAPSHOTS: {
                PARAM_DESCRIPTION:
                PARAM_DESC_COPIED_SNAPSHOTS,
                PARAM_LABEL:
                PARAM_LABEL_COPIED_SNAPSHOTS,
                PARAM_TYPE:
                str,
                PARAM_ALLOWED_VALUES: [
                    COPIED_OWNED_BY_ACCOUNT,
                    COPIED_SNAPSHOTS_SHARED_TO_ACCOUNT, COPIED_SNAPSHOTS_BOTH
                ],
                PARAM_DEFAULT:
                COPIED_OWNED_BY_ACCOUNT,
                PARAM_REQUIRED:
                False
            },
            PARAM_SOURCE_TAGS: {
                PARAM_DESCRIPTION: PARAM_DESC_SOURCE_TAGS,
                PARAM_LABEL: PARAM_LABEL_SOURCE_TAGS,
                PARAM_TYPE: str,
                PARAM_REQUIRED: False
            },
            PARAM_DELETE_AFTER_COPY: {
                PARAM_DESCRIPTION: PARAM_DESC_DELETE_AFTER_COPY,
                PARAM_LABEL: PARAM_LABEL_DELETE_AFTER_COPY,
                PARAM_TYPE: bool,
                PARAM_DEFAULT: False,
                PARAM_REQUIRED: False
            },
            PARAM_ENCRYPTED: {
                PARAM_DESCRIPTION: PARAM_DESC_ENCRYPTED,
                PARAM_LABEL: PARAM_LABEL_ENCRYPTED,
                PARAM_TYPE: bool,
                PARAM_DEFAULT: False,
                PARAM_REQUIRED: False
            },
            PARAM_ACCOUNTS_VOLUME_CREATE_PERMISSIONS: {
                PARAM_DESCRIPTION:
                PARAM_DESC_ACCOUNTS_VOLUME_CREATE_PERMISSIONS,
                PARAM_TYPE: list,
                PARAM_REQUIRED: False,
                PARAM_LABEL: PARAM_LABEL_ACCOUNTS_VOLUME_CREATE_PERMISSIONS
            },
            PARAM_COPY_FROM_OWNER_ACCOUNTS: {
                PARAM_DESCRIPTION: PARAM_DESC_COPY_FROM_OWNER_ACCOUNTS,
                PARAM_LABEL: PARAM_LABEL_COPY_FROM_OWNER_ACCOUNTS,
                PARAM_TYPE: list,
                PARAM_REQUIRED: False
            },
            PARAM_TAG_IN_DESTINATION_ACCOUNT: {
                PARAM_DESCRIPTION: PARAM_DESC_TAG_IN_DESTINATION_ACCOUNT,
                PARAM_TYPE: bool,
                PARAM_REQUIRED: False,
                PARAM_DEFAULT: False,
                PARAM_LABEL: PARAM_LABEL_TAG_IN_DESTINATION_ACCOUNT
            },
            PARAM_DESTINATION_ACCOUNT_TAG_ROLENAME: {
                PARAM_DESCRIPTION: PARAM_DESC_DESTINATION_ACCOUNT_TAG_ROLENAME,
                PARAM_TYPE: str,
                PARAM_REQUIRED: False,
                PARAM_LABEL: PARAM_LABEL_DESTINATION_ACCOUNT_TAG_ROLENAME
            },
            PARAM_TAG_IN_SOURCE_ACCOUNT: {
                PARAM_DESCRIPTION: PARAM_DESC_TAG_IN_SOURCE_ACCOUNT,
                PARAM_TYPE: bool,
                PARAM_REQUIRED: False,
                PARAM_DEFAULT: False,
                PARAM_LABEL: PARAM_LABEL_TAG_IN_SOURCE_ACCOUNT
            },
            PARAM_SOURCE_SHARED_BY_TAGS: {
                PARAM_DESCRIPTION: PARAM_DESC_SOURCE_SHARED_BY_TAGS,
                PARAM_LABEL: PARAM_LABEL_SOURCE_SHARED_BY_TAGS,
                PARAM_TYPE: str,
                PARAM_REQUIRED: False
            },
            PARAM_SOURCE_ACCOUNT_TAG_ROLE_NAME: {
                PARAM_DESCRIPTION: PARAM_DESC_SOURCE_ACCOUNT_TAG_ROLENAME,
                PARAM_TYPE: str,
                PARAM_REQUIRED: False,
                PARAM_LABEL: PARAM_LABEL_SOURCE_ACCOUNT_TAG_ROLENAME
            },
            PARAM_KMS_KEY_ID: {
                PARAM_DESCRIPTION: PARAM_DESC_KMS_KEY_ID,
                PARAM_LABEL: PARAM_LABEL_KMS_KEY_ID,
                PARAM_TYPE: str,
                PARAM_REQUIRED: False
            }
        },
        ACTION_PARAMETER_GROUPS: [{
            ACTION_PARAMETER_GROUP_TITLE:
            GROUP_LABEL_SNAPSHOT_COPY_OPTIONS,
            ACTION_PARAMETER_GROUP_LIST: [
                PARAM_DESTINATION_REGION, PARAM_COPIED_SNAPSHOTS,
                PARAM_COPY_FROM_OWNER_ACCOUNTS, PARAM_SNAPSHOT_DESCRIPTION,
                PARAM_DELETE_AFTER_COPY
            ],
        }, {
            ACTION_PARAMETER_GROUP_TITLE:
            GROUP_LABEL_TAGGING_OPTIONS,
            ACTION_PARAMETER_GROUP_LIST: [
                PARAM_COPIED_SNAPSHOT_TAGS,
                PARAM_SNAPSHOT_TAGS,
            ],
        }, {
            ACTION_PARAMETER_GROUP_TITLE:
            GROUP_LABEL_SOURCE_SNAPSHOT_TAGGING,
            ACTION_PARAMETER_GROUP_LIST: [
                PARAM_SOURCE_TAGS,
                PARAM_TAG_IN_SOURCE_ACCOUNT,
                PARAM_SOURCE_SHARED_BY_TAGS,
                PARAM_SOURCE_ACCOUNT_TAG_ROLE_NAME,
            ],
        }, {
            ACTION_PARAMETER_GROUP_TITLE:
            GROUP_LABEL_SNAPSHOT_SHARING,
            ACTION_PARAMETER_GROUP_LIST: [
                PARAM_ACCOUNTS_VOLUME_CREATE_PERMISSIONS,
                PARAM_TAG_IN_DESTINATION_ACCOUNT,
                PARAM_DESTINATION_ACCOUNT_TAG_ROLENAME
            ],
        }, {
            ACTION_PARAMETER_GROUP_TITLE:
            GROUP_LABEL_ENCRYPTION_AND_PERMISSIONS,
            ACTION_PARAMETER_GROUP_LIST: [PARAM_ENCRYPTED, PARAM_KMS_KEY_ID],
        }],
        ACTION_PERMISSIONS: [
            "ec2:CopySnapshot", "ec2:CreateTags", "ec2:DeleteTags",
            "ec2:DescribeSnapshots", "ec2:DeleteSnapshot",
            "ec2:ModifySnapshotAttribute"
        ]
    }

    @staticmethod
    def marker_tag_source_snapshot_id():
        return MARKER_TAG_SOURCE_SNAPSHOT_ID_TEMPLATE.format(
            os.getenv(handlers.ENV_STACK_NAME))

    @staticmethod
    def marker_tag_copied_to(taskname):
        return MARKER_TAG_COPIED_TO_TEMPLATE.format(
            os.getenv(handlers.ENV_STACK_NAME), taskname)

    # noinspection PyUnusedLocal
    @staticmethod
    def process_and_select_resource(service, logger, resource_name, resource,
                                    context, task, task_assumed_role):
        def get_snapshot_tags(client, snap_id):
            try:
                resp = client.describe_snapshots_with_retries(
                    RestorableByUserIds=["self"], SnapshotIds=[snap_id])
                list_of_tags = resp.get("Snapshots", [{}])[0].get("Tags", [])
                return {
                    tag["Key"].strip(): tag.get("Value", "").strip()
                    for tag in list_of_tags
                }, True

            except Exception as ex:
                if getattr(ex, "response",
                           {}).get("Error",
                                   {}).get("Code",
                                           "") == "InvalidSnapshot.NotFound":
                    return {}, False
                else:
                    raise ex

        def mark_as_being_selected_for_copy(client, snapshot):
            try:
                tag_name = Ec2CopySnapshotAction.marker_tag_copied_to(
                    task[handlers.TASK_NAME])

                # Serial number for copy. This is stored in the tag of the snapshot anf the stored resources in the task
                # Before starting a copy there will be check if these match to avoid double copied of a snapshot
                copy_serial = str(uuid.uuid4())

                tag_data = {
                    tag_name:
                    safe_json({
                        TAG_REGION:
                        task.get(handlers.TASK_PARAMETERS,
                                 {}).get(PARAM_DESTINATION_REGION, ""),
                        COPY_SERIAL_NUMBER:
                        copy_serial,
                        TAG_COPY_SNAPSHOT_ID:
                        ""
                    })
                }

                client.create_tags_with_retries(
                    Resources=[snapshot["SnapshotId"]],
                    Tags=tag_key_value_list(tag_data))

                # store the copy serial number as part of the selected resource
                resource[COPY_SERIAL_NUMBER] = copy_serial

            except Exception as ex:
                logger.warning(WARN_SETTING_COPIED_TAG, snapshot["SnapshotId"],
                               ex)

        # source snapshot
        snapshot_id = resource["SnapshotId"]

        # owner of the snapshot
        snapshot_owner = resource["OwnerId"]

        parameters = task.get(handlers.TASK_PARAMETERS, {})

        # copy owned, shared or both
        copied_snapshot_types = parameters[PARAM_COPIED_SNAPSHOTS]

        this_account = task.get(TASK_THIS_ACCOUNT, False)
        accounts = task.get(TASK_ACCOUNTS, [])

        if this_account and len(accounts) == 0:
            account = os.getenv(handlers.ENV_OPS_AUTOMATOR_ACCOUNT)
        elif not this_account and len(accounts) == 1:
            account = accounts[0]
        else:
            account = services.account_from_role_arn(task_assumed_role)

        if copied_snapshot_types == COPIED_OWNED_BY_ACCOUNT and account != snapshot_owner:
            logger.debug(DEBUG_ONLY_COPY_OWNED_SNAPSHOTS, snapshot_id,
                         snapshot_owner, PARAM_COPIED_SNAPSHOTS, account)
            return None

        if copied_snapshot_types == COPIED_SNAPSHOTS_SHARED_TO_ACCOUNT and account == snapshot_owner:
            logger.debug(DEBUG_ONLY_COPY_SHARED_SNAPSHOTS, snapshot_id,
                         snapshot_owner, PARAM_COPIED_SNAPSHOTS, account)
            return None

        copy_from_accounts = parameters.get(PARAM_COPY_FROM_OWNER_ACCOUNTS,
                                            None)
        if copy_from_accounts not in [None, []]:

            if copied_snapshot_types == COPIED_OWNED_BY_ACCOUNT:
                raise_value_error(ERR_ACCOUNTS_BUT_NOT_SHARED,
                                  PARAM_COPY_FROM_OWNER_ACCOUNTS,
                                  PARAM_COPIED_SNAPSHOTS)
            if snapshot_owner != account and snapshot_owner not in [
                    a.strip() for a in copy_from_accounts
            ]:
                logger.debug(DEBUG_SHARED_SNAPSHOT_OWNER_NOT_IN_LIST,
                             snapshot_id, snapshot_owner,
                             ",".join(copy_from_accounts))
                return None

        # name of tag that is used to mark snapshots being copied
        copied_tag_name = Ec2CopySnapshotAction.marker_tag_copied_to(
            task[handlers.TASK_NAME])

        if copied_tag_name in resource.get("Tags", {}):
            # noinspection PyBroadException
            try:
                logger.debug(
                    "Snapshot already copied or being copied, copy data is:\n  {}",
                    safe_json(
                        json.loads(
                            resource.get("Tags", {}).get(copied_tag_name,
                                                         {}))))
            except Exception:
                pass
            return None

        # ec2 client for getting most current tag values and setting tags
        ec2 = get_client_with_retries(
            service_name="ec2",
            methods=["create_tags", "describe_snapshots"],
            region=resource["Region"],
            context=context,
            session=service.session,
            logger=logger)

        # get the most current tags as they might be changed by overlapping copy tasks
        tags, snapshot_found = get_snapshot_tags(ec2, snapshot_id)

        # snapshot no longer there
        if not snapshot_found:
            logger.debug("Snapshot {} not longer available", snapshot_id)
            return None

        if copied_tag_name in tags:
            return None

        mark_as_being_selected_for_copy(ec2, resource)
        return resource

    # noinspection PyUnusedLocal
    @staticmethod
    def action_validate_parameters(parameters, task_settings, logger):

        valid_regions = services.get_session().get_available_regions(
            "ec2", "aws")
        region = parameters.get(PARAM_DESTINATION_REGION)
        if region not in valid_regions:
            raise_value_error(ERR_INVALID_DESTINATION_REGION, region,
                              ",".join(valid_regions))

        if parameters.get(PARAM_DELETE_AFTER_COPY, False) and parameters.get(
                PARAM_COPIED_SNAPSHOTS) != COPIED_OWNED_BY_ACCOUNT:
            raise_value_error(ERR_CANNOT_DELETE_SHARED_SNAPSHOTS)

        kms_key_id = parameters.get(PARAM_KMS_KEY_ID, None)
        if not parameters[PARAM_ENCRYPTED] and kms_key_id not in ["", None]:
            raise_value_error(ERR_KMS_KEY_ONLY_IF_ENCRYPTED, PARAM_KMS_KEY_ID)

        if kms_key_id not in ["", None]:
            if re.match(KMS_KEY_ID_PATTERN, kms_key_id) is None:
                raise_value_error(ERR_INVALID_KMS_ID_ARN, kms_key_id)

            destination_region = parameters[PARAM_DESTINATION_REGION]
            if kms_key_id.split(":")[3] != destination_region:
                raise_value_error(ERR_KMS_KEY_NOT_IN_REGION, kms_key_id,
                                  destination_region)

        return parameters

    @staticmethod
    def action_logging_subject(arguments, _):
        snapshot = arguments[ACTION_PARAM_RESOURCES]
        account = snapshot["AwsAccount"]
        snapshot_id = snapshot["SnapshotId"]
        region = snapshot["Region"]
        return "{}-{}-{}-{}".format(account, region, snapshot_id,
                                    log_stream_date())

    @staticmethod
    def action_concurrency_key(arguments):
        # copies per account/destination
        return "ec2:CopySnapshot:{}:{}".format(
            arguments[ACTION_PARAM_ACCOUNT],
            arguments[PARAM_DESTINATION_REGION])

    @property
    def ec2_destination_client(self):
        if self._ec2_destination_client is None:
            methods = [
                "copy_snapshot", "create_tags", "delete_tags",
                "modify_snapshot_attribute"
            ]

            self._ec2_destination_client = get_client_with_retries(
                "ec2",
                methods=methods,
                region=self._destination_region_,
                context=self._context_,
                session=self._session_,
                logger=self._logger_)
        return self._ec2_destination_client

    @property
    def ec2_source_client(self):
        if self._ec2_source_client is None:
            methods = ["create_tags", "delete_tags", "delete_snapshot"]

            self._ec2_source_client = get_client_with_retries(
                "ec2",
                methods=methods,
                region=self.source_region,
                context=self._context_,
                session=self._session_,
                logger=self._logger_)
        return self._ec2_source_client

    def __init__(self, action_args, action_parameters):
        self._destination_region_ = None

        ActionBase.__init__(self, action_args, action_parameters)

        # debug and dryrun
        self.snapshot = self._resources_

        # snapshot source and destination information
        self.source_snapshot_id = self.snapshot["SnapshotId"]
        self.source_region = self.snapshot["Region"]
        self.owner = self.snapshot.get("OwnerId", "")

        self.encrypted = self.get(PARAM_ENCRYPTED, False)
        self.kms_key_id = self.get(PARAM_KMS_KEY_ID, None)
        self.accounts_with_create_permissions = self.get(
            PARAM_ACCOUNTS_VOLUME_CREATE_PERMISSIONS, [])

        self.delete_after_copy = self.get(PARAM_DELETE_AFTER_COPY, False)

        # filter for copied tags from source snapshot
        self.copied_volume_tagfiter = TagFilterSet(
            self.get(PARAM_COPIED_SNAPSHOT_TAGS, ""))

        self.tag_snapshots_in_shared_accounts = self.get(
            PARAM_TAG_IN_DESTINATION_ACCOUNT, False)
        self.tag_snapshots_in_source_account = self.get(
            PARAM_TAG_IN_SOURCE_ACCOUNT, False)

        # tagging roles
        self.dest_account_snapshot_tagging_rolename = self.get(
            PARAM_DESTINATION_ACCOUNT_TAG_ROLENAME, "")
        self.source_account_tagging_role_name = self.get(
            PARAM_SOURCE_ACCOUNT_TAG_ROLE_NAME, "")

        volume_id = self.snapshot["VolumeId"]
        if volume_id == DUMMY_VOLUME_IF_FOR_COPIED_SNAPSHOT:
            volume_from_tag = self.snapshot.get("Tags", {}).get(
                actions.marker_snapshot_tag_source_source_volume_id(), None)
            if volume_from_tag is not None:
                volume_id = volume_from_tag
        self.source_volume_id = volume_id

        self._ec2_destination_client = None
        self._ec2_source_client = None

        # setup result with known values
        self.result = {
            "account": self._account_,
            "task": self._task_,
            "destination-region": self._destination_region_,
            "source-region": self.source_region,
            "source-snapshot-id": self.source_snapshot_id,
            "encrypted": self.encrypted,
            "kms-id": self.kms_key_id if self.kms_key_id is not None else ""
        }

    def is_completed(self, snapshot_create_data):
        def delete_source_after_copy():
            self._logger_.info(INF_DELETING_SNAPSHOT, self.source_snapshot_id)
            self.ec2_source_client.delete_snapshot_with_retries(
                SnapshotId=self.source_snapshot_id)
            self._logger_.info(INF_SNAPSHOT_DELETED, self.source_snapshot_id,
                               self.source_region)

        def source_tags(copy_id, source_tags_param):
            snapshot_tags = {}
            snapshot_tags.update(
                self.build_tags_from_template(
                    parameter_name=source_tags_param,
                    region=self.source_region,
                    tag_variables={
                        TAG_PLACEHOLDER_COPIED_SNAPSHOT_ID: copy_id,
                        TAG_PLACEHOLDER_COPIED_REGION:
                        self._destination_region_
                    }))
            return snapshot_tags

        def set_source_snapshot_tags(copy_id):
            snapshot_tags = source_tags(copy_id, PARAM_SOURCE_TAGS)
            if len(snapshot_tags) == 0:
                return

            self._logger_.info(INF_CREATE_SOURCE_TAGS, snapshot_tags,
                               self._account_)

            if len(snapshot_tags) > 0:
                tagging.set_ec2_tags(ec2_client=self.ec2_source_client,
                                     resource_ids=[self.source_snapshot_id],
                                     tags=snapshot_tags,
                                     logger=self._logger_)

                self._logger_.info(INF_TAGS_CREATED)

        def grant_create_volume_permissions(snap_id):

            if self.accounts_with_create_permissions is not None and len(
                    self.accounts_with_create_permissions) > 0:

                args = {
                    "CreateVolumePermission": {
                        "Add": [{
                            "UserId": a.strip()
                        } for a in self.accounts_with_create_permissions]
                    },
                    "SnapshotId": snap_id
                }

                try:
                    self.ec2_destination_client.modify_snapshot_attribute_with_retries(
                        **args)
                    self._logger_.info(
                        INF_SETTING_CREATE_VOLUME_PERMISSIONS,
                        ", ".join(self.accounts_with_create_permissions))
                except Exception as ex:
                    raise_exception(ERR_SETTING_CREATE_VOLUME_PERMISSIONS,
                                    self.accounts_with_create_permissions, ex)

        def tag_shared_snapshots(tags, snap_id):
            # creates tags for snapshots that have been shared in account the snapshots are shared with

            if len(tags) == 0 or not self.tag_snapshots_in_shared_accounts:
                return

            if self.accounts_with_create_permissions in ["", None]:
                return

            for account in self.accounts_with_create_permissions:

                session_for_tagging = self.get_action_session(
                    account=account,
                    param_name=PARAM_DESTINATION_ACCOUNT_TAG_ROLENAME,
                    logger=self._logger_)

                if session_for_tagging is None:
                    self._logger_.error(ERR_TAGS_NOT_SET_IN_ACCOUNT, account)
                    continue

                try:
                    ec2_client = get_client_with_retries(
                        service_name="ec2",
                        methods=["create_tags", "delete_tags"],
                        context=self._context_,
                        region=self.get(PARAM_DESTINATION_REGION),
                        session=session_for_tagging,
                        logger=self._logger_)

                    tagging.set_ec2_tags(ec2_client=ec2_client,
                                         resource_ids=[snap_id],
                                         tags=tags,
                                         logger=self._logger_)

                    self._logger_.info(INF_CREATE_SHARED_TAGS, tags, account)

                except Exception as ex:
                    raise_exception(ERR_SETTING_SHARED_TAGS, account, str(ex))

        def tag_shared_source_snapshot(copy_id):
            # created tags for snapshots for shared snapshots in the source account of the shares snapshots
            snapshot_tags = source_tags(copy_id, PARAM_SOURCE_SHARED_BY_TAGS)
            if len(snapshot_tags
                   ) == 0 or not self.tag_snapshots_in_source_account:
                return

            # only for snapshots that have been shared by other account
            if self.owner == self.get_account_for_task():
                self._logger_.debug(
                    "Account {} is owner, no tags set for snapshot {} in account of owner",
                    self._account_, self.source_snapshot_id)
                return

            session_for_tagging = self.get_action_session(
                account=self.owner,
                param_name=PARAM_SOURCE_ACCOUNT_TAG_ROLE_NAME,
                logger=self._logger_)

            if session_for_tagging is None:
                self._logger_.error(ERR_TAGS_NOT_SET_IN_ACCOUNT, self.owner)
                return

            try:

                self._logger_.info(INF_CREATE_SHARED_ACCOUNT_SNAPSHOT_TAGS,
                                   snapshot_tags, self.source_snapshot_id,
                                   self.owner)
                ec2_client = get_client_with_retries(
                    service_name="ec2",
                    methods=["create_tags", "delete_tags"],
                    context=self._context_,
                    region=self.source_region,
                    session=session_for_tagging,
                    logger=self._logger_)

                tagging.set_ec2_tags(ec2_client=ec2_client,
                                     resource_ids=[self.source_snapshot_id],
                                     tags=snapshot_tags,
                                     logger=self._logger_)

            except Exception as ex:
                raise_exception(ERR_SETTING_SOURCE_SHARED_TAGS, self.owner,
                                str(ex))

        if snapshot_create_data.get("already-copied", False):
            self._logger_.info(INF_COMPLETE_ALREADY_COPIED,
                               self.source_snapshot_id)
            return self.result

        if snapshot_create_data.get("not-longer-available", False):
            self._logger_.info(INF_COMPLETED_NOT_LONGER_AVAILABLE,
                               self.source_snapshot_id)
            return self.result

        # create service instance to test if snapshot exists
        ec2 = services.create_service(
            "ec2",
            session=self._session_,
            service_retry_strategy=get_default_retry_strategy(
                "ec2", context=self._context_))

        copy_snapshot_id = snapshot_create_data["copy-snapshot-id"]
        # test if the snapshot with the id that was returned from the CopySnapshot API call exists and is completed
        copied_snapshot = ec2.get(services.ec2_service.SNAPSHOTS,
                                  region=self._destination_region_,
                                  OwnerIds=["self"],
                                  Filters=[{
                                      "Name": "snapshot-id",
                                      "Values": [copy_snapshot_id]
                                  }])

        if copied_snapshot is not None:
            self._logger_.debug(INF_CHECK_COMPLETED_RESULT, copied_snapshot)

        state = copied_snapshot[
            "State"] if copied_snapshot is not None else None

        if copied_snapshot is None or state == SNAPSHOT_STATE_PENDING:
            self._logger_.info(INF_COPY_PENDING, copy_snapshot_id,
                               self._destination_region_)
            return None

        if state == SNAPSHOT_STATE_ERROR:
            copied_tag_name = Ec2CopySnapshotAction.marker_tag_copied_to(
                self._task_)
            self.ec2_source_client.delete_tags_with_retries(
                Resources=[self.source_snapshot_id],
                Tags=[{
                    "Key": copied_tag_name
                }])
            raise_exception(ERR_COPY_SNAPSHOT)

        if state == SNAPSHOT_STATE_COMPLETED:
            self._logger_.info(INF_COPY_COMPLETED, self.source_snapshot_id,
                               self.source_region, copy_snapshot_id,
                               self._destination_region_)
            grant_create_volume_permissions(copy_snapshot_id)
            tag_shared_snapshots(snapshot_create_data.get("tags", {}),
                                 copy_snapshot_id)
            tag_shared_source_snapshot(copy_snapshot_id)
            if self.delete_after_copy:
                delete_source_after_copy()
            else:
                set_source_snapshot_tags(copy_snapshot_id)

            # wait there for 15 seconds as count the limit for max number of concurrent snapshot copies
            # by the EC2 service is sometimes delayed
            time.sleep(5)

            return copied_snapshot

        return None

    def execute(self):
        def get_tags_for_copied_snapshot():

            snapshot_tags = (
                self.copied_volume_tagfiter.pairs_matching_any_filter(
                    self.snapshot.get("Tags", {})))
            snapshot_tags[actions.marker_snapshot_tag_source_source_volume_id(
            )] = self.source_volume_id
            snapshot_tags.update(
                self.build_tags_from_template(
                    parameter_name=PARAM_SNAPSHOT_TAGS,
                    region=self.source_region,
                    tag_variables={
                        TAG_PLACEHOLDER_SOURCE_SNAPSHOT_ID:
                        self.source_snapshot_id,
                        TAG_PLACEHOLDER_SOURCE_REGION: self.source_region,
                        TAG_PLACEHOLDER_OWNER_ACCOUNT: self.owner,
                        TAG_PLACEHOLDER_SOURCE_VOLUME: self.source_volume_id
                    }))

            snapshot_tags[Ec2CopySnapshotAction.marker_tag_source_snapshot_id(
            )] = self.source_snapshot_id
            snapshot_tags[actions.marker_snapshot_tag_source_source_volume_id(
            )] = self.source_volume_id

            return snapshot_tags

        def get_source_snapshot():
            ec2 = services.create_service(
                "ec2",
                session=self._session_,
                service_retry_strategy=get_default_retry_strategy(
                    "ec2", context=self._context_))

            snapshot = ec2.get(services.ec2_service.SNAPSHOTS,
                               region=self.source_region,
                               RestorableByUserIds=["self"],
                               Filters=[{
                                   "Name": "snapshot-id",
                                   "Values": [self.source_snapshot_id]
                               }])
            return snapshot

        def should_copy_snapshot():
            snapshot = get_source_snapshot()

            # source snapshot was already deleted by tasks that were in wait for execution list
            if snapshot is None:
                self.result["not-longer-available"] = True
                self._logger_.info(INF_WARNING_NO_SNAPSHOT,
                                   self.source_snapshot_id)
                return False

            # get tags from the snapshot, these must have contain the mark_as_copied tag and this tag must contain the same
            # copy serial number as the snapshot that was in the selected resource for this task instance
            source_snapshot_tags = snapshot.get(
                "Tags", {}) if snapshot is not None else {}
            marked_as_copied_tag = Ec2CopySnapshotAction.marker_tag_copied_to(
                self._task_)
            if marked_as_copied_tag in source_snapshot_tags:
                snapshot_copy_data = json.loads(
                    source_snapshot_tags[marked_as_copied_tag])
            else:
                snapshot_copy_data = {}
            if snapshot_copy_data.get(
                    COPY_SERIAL_NUMBER,
                    "") != self.snapshot.get(COPY_SERIAL_NUMBER):
                self._logger_.info(INF_COPIED_BY_OTHER,
                                   snapshot_copy_data.get(TAG_REGION, ""),
                                   snapshot_copy_data(COPY_SERIAL_NUMBER, ""))
                self.result["already-copied"] = True
                self.result["copied-data"] = snapshot_copy_data
                return False
            return True

        # logged information
        self._logger_.info("{}, version {}", self.properties[ACTION_TITLE],
                           self.properties[ACTION_VERSION])
        self._logger_.info(INF_ACCOUNT_SNAPSHOT, self.source_snapshot_id,
                           self._account_, self.source_region,
                           self._destination_region_)
        self._logger_.debug("Snapshot : {}", self.snapshot)

        boto_call = "copy_snapshot"
        try:
            # setup argument for CopySnapshot call
            args = {
                "SourceRegion": self.source_region,
                "SourceSnapshotId": self.source_snapshot_id
            }

            if not should_copy_snapshot():
                return self.result

            if self.encrypted:
                args["Encrypted"] = True
                self.result["encrypted"] = True
                if self.kms_key_id not in ["", None]:
                    args["KmsKeyId"] = self.kms_key_id

            if self._dryrun_:
                args["DryRun"] = True

            source_description = self.snapshot.get("Description", "")

            description_variables = {
                TAG_PLACEHOLDER_SOURCE_SNAPSHOT_ID: self.source_snapshot_id,
                TAG_PLACEHOLDER_SOURCE_REGION: self.source_region,
                TAG_PLACEHOLDER_OWNER_ACCOUNT: self.owner,
                TAG_PLACEHOLDER_SOURCE_VOLUME: self.source_volume_id,
                TAG_PLACEHOLDER_SOURCE_DESCRIPTION: source_description
            }

            args["Description"] = self.build_str_from_template(
                parameter_name=PARAM_SNAPSHOT_DESCRIPTION,
                region=self.source_region,
                tag_variables=description_variables)
            if args["Description"] == "":
                args["Description"] = source_description

            # start the copy
            resp = self.ec2_destination_client.copy_snapshot_with_retries(
                **args)

            # id of the copy
            copy_snapshot_id = resp.get("SnapshotId")
            self._logger_.info(INF_SNAPSHOT_COPIED, self.source_snapshot_id,
                               self._destination_region_, copy_snapshot_id)
            self.result[boto_call] = resp
            self.result["copy-snapshot-id"] = copy_snapshot_id

            # update the tag that marks the snapshot as being copied
            boto_call = "create_tags (source)"
            copied_tag_name = Ec2CopySnapshotAction.marker_tag_copied_to(
                self._task_)

            copy_data_tag = {
                copied_tag_name:
                safe_json({
                    TAG_REGION:
                    self._destination_region_,
                    COPY_SERIAL_NUMBER:
                    self.snapshot.get(COPY_SERIAL_NUMBER, ""),
                    TAG_COPIED_BY_TASK:
                    self.get(ACTION_PARAM_TASK_ID, ""),
                    TAG_COPY_SNAPSHOT_ID:
                    copy_snapshot_id
                })
            }

            self.ec2_source_client.create_tags_with_retries(
                Resources=[self.source_snapshot_id],
                Tags=tag_key_value_list(copy_data_tag))

            # set tags on the copy
            boto_call = "create_tags (target)"
            tags = get_tags_for_copied_snapshot()
            self._logger_.info(INF_CREATE_COPIED_TAGS, tags, self._account_)

            if len(tags) > 0:
                tagging.set_ec2_tags(ec2_client=self.ec2_destination_client,
                                     resource_ids=[copy_snapshot_id],
                                     tags=tags,
                                     logger=self._logger_)

                self.result["tags"] = tags
                self._logger_.info(INF_TAGS_CREATED)

        except Exception as ex:
            if self._dryrun_:
                self._logger_.debug(str(ex))
                self.result[boto_call] = str(ex)
                return self.result
            else:
                raise ex

        self.result[METRICS_DATA] = build_action_metrics(self,
                                                         CopiedSnapshots=1)

        return self.result
 def service_regions(self):
     """
     Regions that can be used for this service, return all AWS regions (assuming they all support EC2)
     :return: Service regions
     """
     return services.get_session().get_available_regions(service_name="ec2")
Beispiel #14
0
    def _get_tags_for_resource(self, client, resource):
        """
        Returns the tags for specific resources that require additional boto calls to retrieve their tags.
        :param client: Client that can be used to make the boto call to retrieve the tags
        :param resource: The resource for which to retrieve the tags
        :return: Tags
        """

        # get the name of the proprty that holds the arn of the resource
        arn_property_name = "{}Arn".format(self._resource_name[0:-1])
        if arn_property_name[0:2].lower() == "db":
            arn_property_name = "DB{}".format(arn_property_name[2:])
        # get the arn of the resource
        resource_arn = resource[arn_property_name]

        # owner of the resource (could be other account for shared sbapshots)
        resource_owner_account = resource_arn.split(":")[4]
        resource_region = resource_arn.split(":")[3]

        if resource_owner_account == self.aws_account:
            # sane account, can use same session as used to retrieve the resource
            if self._use_cached_tags:
                self._tag_session = self.session

            # make sure the client has retries
            if getattr(self._service_client, "list_tags_for_resource" + boto_retry.DEFAULT_SUFFIX, None) is None:
                boto_retry.make_method_with_retries(boto_client_or_resource=client,
                                                    name="list_tags_for_resource",
                                                    service_retry_strategy=self._service_retry_strategy)
                self._tag_rds_client = client
        else:
            # resource is from other account, get a session to get the tags from that account as these are not
            # visible for shared rds resources
            if self._tag_account != resource_owner_account or self._tag_session is None:
                self._tag_account = resource_owner_account
                used_tag_role = None
                if self._tag_roles is not None:
                    # see if there is a role for the owner account
                    for role in self._tag_roles:
                        if role is not None and services.account_from_role_arn(role) == resource_owner_account:
                            used_tag_role = role
                            break
                    else:
                        # if there is no role and the account is the ops automator account use the default role
                        # in other cases it is not possible to retrieve the tags
                        if resource_owner_account != os.getenv(handlers.ENV_OPS_AUTOMATOR_ACCOUNT):
                            return {}
                self._tag_session = services.get_session(role_arn=used_tag_role)

            if not self._use_cached_tags:
                self._tag_rds_client = boto_retry.get_client_with_retries("rds", methods=["list_tags_for_resource"],
                                                                          context=self._context, region=resource_region)

        if self._use_cached_tags:
            return self.cached_tags(session=self._tag_session,
                                    resource_name=RESOURCES_WITH_TAGS[resource["ResourceTypeName"]],
                                    region=resource_region).get(resource_arn, {})

        try:
            resp = self._tag_rds_client.list_tags_for_resource_with_retries(ResourceName=resource_arn)
            return resp.get("TagList", [])
        except botocore.exceptions.ClientError as ex:
            if getattr(ex, "response", {}).get("Error", {}).get("Code", "") == "InvalidParameterValue":
                return []
            raise_exception("Can not list rds tags for resource {}, {}", resource_arn, ex)
Beispiel #15
0
    def handle_request(self, use_custom_select=True):
        """
        Handled the cloudwatch rule timer event
        :return: Started tasks, if any, information
        """
        try:

            self._logger.info("Handling CloudWatch event {}",
                              safe_json(self._event, indent=3))

            result = []
            start = datetime.now()

            dt = self._event_time()
            config_task = None

            source_resource_tags = None

            try:

                # for all events tasks in configuration
                for config_task in TaskConfiguration(
                        context=self._context,
                        logger=self._logger).get_tasks():

                    self._logger.debug_enabled = config_task.get(
                        handlers.TASK_DEBUG, False)

                    if not self._event_triggers_task(task=config_task):
                        continue

                    # tasks that can react to events with a wider resource scope than the actual resource causing the event may
                    # have a filter that can is used to filter based on the tags of the resource
                    event_source_tag_filter = config_task.get(
                        handlers.TASK_EVENT_SOURCE_TAG_FILTER, None)
                    if event_source_tag_filter is not None:
                        if source_resource_tags is None:
                            # get the tags for the source resource of the event
                            session = services.get_session(
                                self._role_executing_triggered_task,
                                logger=self._logger)
                            if session is None:
                                self._logger.error(
                                    ERR_NO_SESSION_FOR_GETTING_TAGS)
                                continue
                            try:
                                source_resource_tags = self._source_resource_tags(
                                    session, config_task)
                            except Exception as ex:
                                self._logger.error(
                                    ERR_GETTING_EVENT_SOURCE_RESOURCE_TAGS, ex)
                                continue

                            self._logger.debug(
                                "Tags for event source resource are  {}",
                                source_resource_tags)

                        # apply filter to source resource tags
                        if not TagFilterExpression(
                                event_source_tag_filter).is_match(
                                    source_resource_tags):
                            self._logger.debug(
                                "Tags of source resource do not match tag filter {}",
                                event_source_tag_filter)
                            continue

                    task_name = config_task[handlers.TASK_NAME]
                    result.append(task_name)

                    select_parameters = self._select_parameters(
                        self._event_name(), config_task)
                    if select_parameters is None:
                        continue

                    self._logger.debug(DEBUG_EVENT, task_name,
                                       self._event_name(), select_parameters,
                                       self._event_account(),
                                       self._event_region(),
                                       safe_json(config_task, indent=3))

                    # create an event for lambda function that scans for resources for this task
                    lambda_event = {
                        handlers.HANDLER_EVENT_ACTION:
                        handlers.HANDLER_ACTION_SELECT_RESOURCES,
                        handlers.HANDLER_EVENT_CUSTOM_SELECT:
                        use_custom_select,
                        handlers.HANDLER_SELECT_ARGUMENTS: {
                            handlers.HANDLER_EVENT_REGIONS:
                            [self._event_region()],
                            handlers.HANDLER_EVENT_ACCOUNT:
                            self._event_account(),
                            handlers.HANDLER_EVENT_RESOURCE_NAME:
                            config_task[handlers.TASK_RESOURCE_TYPE],
                        },
                        handlers.HANDLER_EVENT_SOURCE:
                        "{}:{}:{}".format(self._handled_event_source,
                                          self._handled_detail_type,
                                          self._event_name()),
                        handlers.HANDLER_EVENT_TASK:
                        config_task,
                        handlers.HANDLER_EVENT_TASK_DT:
                        dt
                    }

                    for i in select_parameters:
                        lambda_event[handlers.HANDLER_SELECT_ARGUMENTS][
                            i] = select_parameters[i]

                    if self._event_resources() is not None:
                        self._logger.debug(
                            DEBUG_EVENT_RESOURCES,
                            safe_json(self._event_resources(), indent=3))
                        lambda_event[
                            handlers.
                            HANDLER_SELECT_RESOURCES] = self._event_resources(
                            )

                    if not handlers.running_local(self._context):
                        # start lambda function to scan for task resources
                        payload = str.encode(safe_json(lambda_event))
                        client = get_client_with_retries("lambda", ["invoke"],
                                                         context=self._context,
                                                         logger=self._logger)
                        client.invoke_with_retries(
                            FunctionName=self._context.function_name,
                            InvocationType="Event",
                            LogType="None",
                            Payload=payload)
                    else:
                        # or if not running in lambda environment pass event to main task handler
                        lambda_handler(lambda_event, None)

                return safe_dict({
                    "datetime":
                    datetime.now().isoformat(),
                    "running-time": (datetime.now() - start).total_seconds(),
                    "event-datetime":
                    dt,
                    "started-tasks":
                    result
                })

            except ValueError as ex:
                self._logger.error(ERR_HANDLING_EVENT_IN_BASE_HANDLER, ex,
                                   safe_json(config_task, indent=2))

        finally:
            self._logger.flush()
 def sqs_client(self):
     if self._sqs_client is None:
         self._sqs_client = services.get_session().client("sqs")
     return self._sqs_client
 def region(self):
     return self._args[
         "region"] if "region" in self._args else services.get_session(
         ).region_name
    def _get_tasks_to_execute(self):

        task_items = self._build_tasks_for_selected_resources()

        for item in task_items:
            event = {i: item.get(i) for i in item}
            event[handlers.
                  HANDLER_EVENT_ACTION] = handlers.HANDLER_ACTION_EXECUTE
            event[actions.ACTION_SERVICE] = actions.get_action_properties(
                item[handlers.TASK_TR_ACTION]).get(actions.ACTION_SERVICE)
            action_argument = {
                actions.ACTION_PARAM_CONTEXT:
                self.context,
                actions.ACTION_PARAM_EVENT:
                event,
                actions.ACTION_PARAM_SESSION:
                services.get_session(
                    role_arn=item.get(handlers.TASK_TR_ASSUMED_ROLE, None)),
                actions.ACTION_PARAM_RESOURCES:
                handlers.get_item_resource_data(item, context=self.context),
                actions.ACTION_PARAM_DEBUG:
                item[handlers.TASK_TR_DEBUG],
                actions.ACTION_PARAM_DRYRUN:
                item[handlers.TASK_TR_DRYRUN],
                actions.ACTION_PARAM_TASK_ID:
                item[handlers.TASK_TR_ID],
                actions.ACTION_PARAM_TASK:
                item[handlers.TASK_TR_NAME],
                actions.ACTION_PARAM_TASK_TIMEZONE:
                item[handlers.TASK_TR_TIMEZONE],
                actions.ACTION_PARAM_STACK:
                self.action_stack.stack_name
                if self.action_stack is not None else None,
                actions.ACTION_PARAM_STACK_ID:
                self.action_stack.stack_id
                if self.action_stack is not None else None,
                actions.ACTION_PARAM_STACK_RESOURCES:
                self.action_stack_resources,
                actions.ACTION_PARAM_ASSUMED_ROLE:
                item.get(handlers.TASK_TR_ASSUMED_ROLE),
                actions.ACTION_PARAM_STARTED_AT:
                item[handlers.TASK_TR_STARTED_TS],
                actions.ACTION_PARAM_TAGFILTER:
                item[handlers.TASK_TR_TAGFILTER],
                actions.ACTION_PARAM_TIMEOUT:
                item[handlers.TASK_TR_TIMEOUT],
                actions.ACTION_PARAM_LOGGER:
                self.logger,
                actions.ACTION_PARAM_EVENTS:
                self._events,
                actions.ACTION_PARAM_HAS_COMPLETION:
                item[handlers.TASK_TR_HAS_COMPLETION],
                actions.ACTION_PARAM_INTERVAL:
                item[handlers.TASK_INTERVAL]
            }

            if self._assumed_role is None:
                self._assumed_role = action_argument[
                    actions.ACTION_PARAM_ASSUMED_ROLE]

            action_instance = self.action_class(
                action_argument, item.get(handlers.TASK_TR_PARAMETERS, {}))

            self.verify_log_subject(action_argument)
            self.verify_concurrency(action_argument, item)

            yield action_instance