示例#1
0
    def _validate_auth(self):
        """Method for validating the authentication dictionary retrieved from
        AWS Parameter Store

        Returns:
            bool: Indicator of successful validation
        """
        if not self._config:
            raise AppIntegrationConfigError(
                'Config for service \'{}\' is empty'.format(self.type()))

        # The config validates that the 'auth' dict was loaded, but do a safety check here
        if not self._config.auth:
            raise AppIntegrationConfigError(
                'Auth config for service \'{}\' is empty'.format(self.type()))

        # Get the required authentication keys from the info returned by the subclass
        required_keys = set(self.required_auth_info())
        auth_key_diff = required_keys.difference(set(self._config.auth))
        if not auth_key_diff:
            return

        missing_auth_keys = ', '.join('\'{}\''.format(key)
                                      for key in auth_key_diff)
        raise AppIntegrationConfigError(
            'Auth config for service \'{}\' is missing the following '
            'required keys: {}'.format(self.type(), missing_auth_keys))
示例#2
0
    def evaluate_interval(self):
        """Get the interval at which this function is executing. This translates
        an AWS Rate Schedule Expression ('rate(2 hours)') into a second interval
        """
        if 'interval' not in self:
            raise AppIntegrationConfigError(
                'The \'interval\' value is not defined in the config')

        rate_match = AWS_RATE_RE.match(self['interval'])

        if not rate_match:
            raise AppIntegrationConfigError('Invalid \'rate\' interval value: '
                                            '{}'.format(self['interval']))

        value = rate_match.group(2) or rate_match.group(4)
        unit = rate_match.group(3) or rate_match.group(5).replace('s', '')

        translate_to_seconds = {
            'minute': 60,
            'hour': 60 * 60,
            'day': 60 * 60 * 24
        }

        interval = int(value) * translate_to_seconds[unit]

        LOGGER.debug('Evaluated rate interval: %d seconds', interval)

        # Get the total seconds that this rate evaluates to
        return interval
示例#3
0
    def _get_parameters(names):
        """Simple helper function to house the boto3 ssm client get_parameters operations

        Args:
            names (list): A list of parameter names to retrieve from the aws ssm
                parameter store

        Returns:
            tuple (dict, list): Dictionary with the load parameter names as keys
                and the actual parameter (as a dictionary) as the value. The seconary
                list that is returned contains any invalid parameters that were not loaded
        """
        LOGGER.debug('Retrieving values from parameter store with names: %s',
                     ', '.join('\'{}\''.format(name) for name in names))
        try:
            parameters = AppConfig.SSM_CLIENT.get_parameters(
                Names=names, WithDecryption=True)
        except ClientError as err:
            joined_names = ', '.join('\'{}\''.format(name) for name in names)
            raise AppIntegrationConfigError(
                'Could not get parameter with names {}. Error: '
                '{}'.format(joined_names, err.response['Error']['Message']))

        decoded_params = {}
        for param in parameters['Parameters']:
            try:
                decoded_params[param['Name']] = json.loads(param['Value'])
            except ValueError:
                raise AppIntegrationConfigError(
                    'Could not load value for parameter with '
                    'name \'{}\'. The value is not valid json: '
                    '\'{}\''.format(param['Name'], param['Value']))

        return decoded_params, parameters['InvalidParameters']
示例#4
0
    def _validate_config(self):
        """Validate the top level of the config to make sure it has all the right keys

        Raises:
            AppIntegrationConfigError: If the config is invalid, this exception is raised
        """
        if not self:
            raise AppIntegrationConfigError('App config is empty')

        required_keys = self.required_base_config_keys()
        required_keys.update({'region', 'account_id', 'function_name', 'qualifier', 'auth'})

        config_key_diff = required_keys.difference(set(self))
        if not config_key_diff:
            return

        missing_config_keys = ', '.join('\'{}\''.format(key) for key in config_key_diff)
        raise AppIntegrationConfigError('App config is missing the following required '
                                        'keys: {}'.format(missing_config_keys))
示例#5
0
    def load_config(cls, context, event):
        """Load the configuration for this app invocation

        Args:
            context (LambdaContext): The AWS LambdaContext object, passed in via the handler.

        Returns:
            AppConfig: Subclassed dictionary with the below structure that contains all of the
                 methods for configuration validation, updating, saving, etc:
                    {
                        'type': <type>,
                        'cluster': <cluster>,
                        'prefix': <prefix>,
                        'app_name': <app_name>,
                        'interval': <rate_interval>,
                        'region': <aws_region>,
                        'account_id': <aws_account_id>,
                        'function_name': <function_name>,
                        'qualifier': <qualifier>,
                        'last_timestamp': <time>,
                        'current_state': <running|succeeded|failed>,
                        'auth': {
                            'req_auth_item_01': <req_auth_value_01>
                        }
                    }
        """
        # Load the base config from the context that will get updated with other info
        base_config = AppConfig._parse_context(context)

        LOGGER.debug('Loaded env config: %s', base_config)

        # Create the ssm boto3 client that will be cached and used throughout this execution
        # if one does not exist already
        if AppConfig.SSM_CLIENT is None:
            AppConfig.SSM_CLIENT = boto3.client(
                'ssm', region_name=base_config['region'])

        # Generate a map of all the suffixes and full parameter names
        param_names = {
            key: '_'.join([base_config['function_name'], key])
            for key in {
                cls.AUTH_CONFIG_SUFFIX, cls.BASE_CONFIG_SUFFIX,
                cls.STATE_CONFIG_SUFFIX
            }
        }

        LOGGER.debug('Parameter suffixes and names: %s', param_names)

        # Get the loaded parameters and a list of any invalid ones from parameter store
        params, invalid_params = AppConfig._get_parameters(
            param_names.values())
        LOGGER.debug(
            'Retrieved parameters from parameter store: %s',
            cls._scrub_auth_info(params, param_names[cls.AUTH_CONFIG_SUFFIX]))
        LOGGER.debug(
            'Invalid parameters could not be retrieved from parameter store: %s',
            invalid_params)

        # Check to see if there are any required parameters in the invalid params list
        missing_required_params = [
            param for param in invalid_params
            if param != param_names[cls.STATE_CONFIG_SUFFIX]
        ]

        if missing_required_params:
            joined_params = ', '.join('\'{}\''.format(param)
                                      for param in missing_required_params)
            raise AppIntegrationConfigError(
                'Could not load parameters required for this '
                'configuration: {}'.format(joined_params))

        # Update the env config with the base config values
        base_config.update(params[param_names[cls.BASE_CONFIG_SUFFIX]])

        # The state config can be None with first time deploys, so us a lookup and
        # add default empty values if there is no state found
        base_config.update(
            params.get(param_names[cls.STATE_CONFIG_SUFFIX], {
                cls._STATE_KEY: None,
                cls._TIME_KEY: None
            }))

        # Add the auth config info to the 'auth' key since these key/values can vary
        # from service to service
        base_config[cls.AUTH_CONFIG_SUFFIX] = {
            key: value.encode('utf-8') if isinstance(value, unicode) else value
            for key, value in params[param_names[
                cls.AUTH_CONFIG_SUFFIX]].iteritems()
        }

        return AppConfig(base_config, event)