Beispiel #1
0
    def _read_source(self):
        """Reads in a RightScript source file.

        Reads in the file contents, and swaps in any tokens that may have
        been left in the script.

        args:
            source: Path to the script

        returns:
            <string contents of the script>
        """
        source = self.option('source')

        try:
            fh = open(source)
            raw = fh.read()
        except IOError as e:
            raise exceptions.InvalidOptions('Error reading script %s: %s' %
                                            (source, e))

        try:
            parsed = utils.populate_with_tokens(raw, self._init_tokens)
        except LookupError as e:
            raise exceptions.InvalidOptions('Error parsing tokens in %s: %s' %
                                            (source, e))

        return parsed
Beispiel #2
0
    def _fill_in_contexts(self, context={}, strict=True):
        """Parses self._options and updates it with the supplied context.

        Parses the objects self._options dict (by converting it into a JSON
        string, substituting, and then turning it back into a dict) and the
        self._desc string and replaces any {KEY}s with the valoues from the
        context dict that was supplied.

        Args:
            strict: bool whether or not to allow missing context keys to be
                    skipped over.

        Raises:
            exceptions.InvalidOptions
        """
        # Inject contexts into Description
        try:
            self._desc = utils.populate_with_tokens(
                str(self),
                context,
                self.left_context_separator,
                self.right_context_separator,
                strict=strict)
        except LookupError as e:
            msg = 'Context for description failed: %s' % e
            raise exceptions.InvalidOptions(msg)

        # Inject contexts into condition
        try:
            self._condition = utils.populate_with_tokens(
                str(self._condition),
                context,
                self.left_context_separator,
                self.right_context_separator,
                strict=strict)
        except LookupError as e:
            msg = 'Context for condition failed: %s' % e
            raise exceptions.InvalidOptions(msg)

        # Convert our self._options dict into a string for fast parsing
        options_string = json.dumps(self._options)

        # Generate a new string with the values parsed out. At this point, if
        # any value is un-matched, an exception is raised and execution fails.
        # This stops execution during a dry run, before any live changes are
        # made.
        try:
            new_options_string = utils.populate_with_tokens(
                options_string,
                context,
                self.left_context_separator,
                self.right_context_separator,
                strict=strict)
        except LookupError as e:
            msg = 'Context for options failed: %s' % e
            raise exceptions.InvalidOptions(msg)

        # Finally, convert the string back into a dict and store it.
        self._options = json.loads(new_options_string)
Beispiel #3
0
    def _get_mci_setting_def(self, settings):
        """Returns a fully populated set of Multi Cloud Image Settings.

        See http://reference.rightscale.com/api1.5/
        resources/ResourceMultiCloudImageSettings.html for details.

        This method takes in a dictionary with as set of parameters (cloud,
        image, instance_type, user_data) and returns a fully ready-to-use
        set of RightScale-formatted parameters to update that image. The method
        handles discovering the RightScale HREFs for the cloud, image and
        instance_type options.

        Args:
            settings: A dictionary with the keys: cloud, image,
            instance_type, user_data

        Returns:
            A RightScale-formatted array of tuples.
        """

        # Get our cloud object first -- its required so that we can search for
        # the image/ramdisk/etc hrefs.
        cloud = yield self._client.find_by_name_and_keys(
            collection=self._client._client.clouds, name=settings['cloud'])
        if not cloud:
            raise exceptions.InvalidOptions('Invalid Cloud name supplied: %s' %
                                            settings['cloud'])

        # Find our image by searching for the resource_uid that matches.
        image = yield self._client.find_by_name_and_keys(
            collection=cloud.images, resource_uid=settings['image'])
        if not image:
            raise exceptions.InvalidOptions(
                'Invalid cloud image name supplied: %s' % settings['image'])

        # Find our instance type now too
        instance = yield self._client.find_by_name_and_keys(
            collection=cloud.instance_types, name=settings['instance_type'])
        if not instance:
            raise exceptions.InvalidOptions(
                'Invalid cloud instance_type supplied: %s' %
                settings['instance_type'])

        # Generate our mci parameters, and each of the image settings
        # parameters. This validates that our inputs are all correct one last
        # time.
        params = {
            'cloud_href': cloud.href,
            'image_href': image.href,
            'instance_type_href': instance.href,
        }
        if settings.get('user_data'):
            params['user_data'] = settings['user_data']

        definition = self._generate_rightscale_params(
            prefix='multi_cloud_image_setting', params=params)

        self.log.debug('Prepared MCI Image Definition: %s' % definition)
        raise gen.Return(definition)
Beispiel #4
0
    def _generate_bindings(self, bindings, sequence):
        new_bindings = []

        if not bindings:
            raise gen.Return(new_bindings)

        position = 0
        for config in bindings:
            position += 1

            self.log.debug('Searching for %s (rev: %s)' %
                           (config['right_script'], config.get('rev')))

            raw = yield self._client.find_by_name_and_keys(
                collection=self._client._client.right_scripts,
                exact=True,
                name=config['right_script'])

            # If we got nothing back (empty list, or None), throw an exception
            if raw is None or not raw:
                raise exceptions.InvalidOptions(
                    'Ubable to find RightScript: %s' % config['right_script'])

            # If only one item is returned, and the user didn't explicitly set
            # the revision number, then we pick one for them. If they _did_
            # explicitly set the revision number, we'll fail gracefully.
            if not isinstance(raw, list):
                if raw.soul['revision'] is not config.get('rev'):
                    raise exceptions.InvalidOptions(
                        'Invalid Binding Config: %s' % config)

                new_bindings.append({
                    'position': position,
                    'right_script_href': raw.href,
                    'sequence': sequence
                })
                continue
            else:
                # Ok, if we got a list back, we need to search through the list
                # and look for the desired revision. Again, if the user
                # specified one explicitly, then we'll look for that.
                # Otherwise, we pick the "latest" release (HEAD/0).
                try:
                    res = [
                        res for res in raw
                        if res.soul['revision'] == config.get('rev')
                    ][0]
                    new_bindings.append({
                        'position': position,
                        'right_script_href': res.href,
                        'sequence': sequence
                    })
                    continue
                except IndexError:
                    raise exceptions.InvalidOptions(
                        'Invalid Binding Config: %s' % config)

        new_bindings.sort(key=lambda x: x['position'])
        raise gen.Return(new_bindings)
Beispiel #5
0
    def __init__(self, *args, **kwargs):
        """Check required environment variables."""
        super(WaitForPackage, self).__init__(*args, **kwargs)

        try:
            re.compile(self.option('name'))
        except re.error:
            raise exceptions.InvalidOptions('name is an invalid regex')

        try:
            re.compile(self.option('version'))
        except re.error:
            raise exceptions.InvalidOptions('version is an invalid regex')
Beispiel #6
0
    def _execute(self):

        dep = yield self._find_deployment(self.option('name'))
        if dep:
            raise exceptions.InvalidOptions('Deployment "%s" already exists.' %
                                            self.option('name'))

        params = {
            'name': self.option('name'),
            'description': self.option('description')
        }

        if self.option('server_tag_scope'):
            params['server_tag_scope'] = self.option('server_tag_scope')

        params = self._generate_rightscale_params('deployment', params)

        if self._dry:
            self.log.info('Would create a deployment %s' % self.option('name'))
            self.log.debug('Deployment params: %s' % params)
            raise gen.Return()

        self.log.info('Creating deployment %s' % self.option('name'))

        yield self._client.create_resource(self._client._client.deployments,
                                           params)
Beispiel #7
0
    def _load_task_definition(task_definition_file, tokens, default_tokens={}):
        """Loads and verifies a task definition template file, interpolates
        tokens, and optionally default tokens which may contain environment
        variables.

        Args:
            task_definition_file: task definition file to load, or None.
            tokens: dict of key/value pairs to interpolate into the file.
            default_tokens: dict of default key/value pairs to merge
                with tokens

        Returns:
            Resulting task definition dict or
                None if task_definition_file is None.
        """
        if not task_definition_file:
            return None

        # Defined Kingpin tokens will override environment variables.
        final_tokens = default_tokens.copy()
        final_tokens.update(tokens)

        task_definition = utils.convert_script_to_dict(
            task_definition_file, final_tokens)

        try:
            jsonschema.validate(task_definition,
                                TASK_DEFINITION_SCHEMA)
        except jsonschema.exceptions.ValidationError as e:
            raise exceptions.InvalidOptions(e)
        return task_definition
Beispiel #8
0
    def __init__(self, *args, **kwargs):
        """Check for required settings."""

        super(AWSBaseActor, self).__init__(*args, **kwargs)

        if not (aws_settings.AWS_ACCESS_KEY_ID
                and aws_settings.AWS_SECRET_ACCESS_KEY):
            raise exceptions.InvalidCredentials(
                'AWS settings imported but not all credentials are supplied. '
                'AWS_ACCESS_KEY_ID: %s, AWS_SECRET_ACCESS_KEY: %s' %
                (aws_settings.AWS_ACCESS_KEY_ID,
                 aws_settings.AWS_SECRET_ACCESS_KEY))

        # Establish connection objects that don't require a region
        self.iam_conn = boto.iam.connection.IAMConnection(
            aws_access_key_id=aws_settings.AWS_ACCESS_KEY_ID,
            aws_secret_access_key=aws_settings.AWS_SECRET_ACCESS_KEY)

        # Establish region-specific connection objects.
        region = self.option('region')
        if not region:
            return

        # In case a zone was provided instead of region we can convert
        # it on the fly
        zone_check = re.match(r'(.*[0-9])([a-z]*)$', region)

        if zone_check and zone_check.group(2):
            zone = region  # Only saving this for the log below

            # Set the fixed region
            region = zone_check.group(1)
            self.log.warning('Converting zone "%s" to region "%s".' %
                             (zone, region))

        region_names = [r.name for r in boto.ec2.elb.regions()]
        if region not in region_names:
            err = ('Region "%s" not found. Available regions: %s' %
                   (region, region_names))
            raise exceptions.InvalidOptions(err)

        self.ec2_conn = boto.ec2.connect_to_region(
            region,
            aws_access_key_id=aws_settings.AWS_ACCESS_KEY_ID,
            aws_secret_access_key=aws_settings.AWS_SECRET_ACCESS_KEY)

        self.elb_conn = boto.ec2.elb.connect_to_region(
            region,
            aws_access_key_id=aws_settings.AWS_ACCESS_KEY_ID,
            aws_secret_access_key=aws_settings.AWS_SECRET_ACCESS_KEY)

        self.cf_conn = boto.cloudformation.connect_to_region(
            region,
            aws_access_key_id=aws_settings.AWS_ACCESS_KEY_ID,
            aws_secret_access_key=aws_settings.AWS_SECRET_ACCESS_KEY)

        self.sqs_conn = boto.sqs.connect_to_region(
            region,
            aws_access_key_id=aws_settings.AWS_ACCESS_KEY_ID,
            aws_secret_access_key=aws_settings.AWS_SECRET_ACCESS_KEY)
Beispiel #9
0
    def __init__(self, *args, **kwargs):
        """Initializes all of the sub actors.

        By actually initializing all of the Actors supplied to us during the
        __init__, we effectively do a full instantiation of every Actor defined
        in the supplied JSON all at once and upfront long before we try to
        execute any code. This greatly increases our chances of catching JSON
        errors because every single object is pre-initialized before we ever
        begin executing any of our steps.

        *Note about init_tokens:*
          The group.BaseActor and misc.Macro actors support the concept of
          externally supplied data (usually os.environ) being used as available
          tokens for %TOKEN% parsing when reading JSON/YAML scripts. By passing
          this data between these three actors, we are able to allow nested
          token passing.

          See `Token-replacement <basicuse.html#token-replacement>` for more
          info.
        """
        super(BaseGroupActor, self).__init__(*args, **kwargs)

        # DEPRECATE IN v0.5.0
        if type(self.option('contexts')) == dict:
            try:
                filename = self.option('contexts').get('file', '')
                open(filename)
            except IOError as e:
                self.log.error('Option `contexts` must have valid `file`. '
                               'Received: %s' % filename)
                raise exceptions.InvalidOptions(e)
        # END DEPRECATION

        # Pre-initialize all of our actions!
        self._actions = self._build_actions()
Beispiel #10
0
    def __init__(self, *args, **kwargs):
        """Check required environment variables."""
        super(Delete, self).__init__(*args, **kwargs)

        try:
            re.compile(self.option('packages_to_delete'))
        except re.error:
            raise exceptions.InvalidOptions(
                'packages_to_delete is an invalid regex')
Beispiel #11
0
    def __init__(self, *args, **kwargs):
        """Validate the user-supplied parameters at instantiation time."""

        super(Create, self).__init__(*args, **kwargs)

        allowed_scopes = ('deployment', 'account', '')

        scope = self.option('server_tag_scope')
        if scope not in allowed_scopes:
            raise exceptions.InvalidOptions(
                'server_tag_scope "%s" is not one of: %s' % (scope,
                                                             allowed_scopes))
Beispiel #12
0
    def method(self, *args, **kwargs):
        # We don't support un-named args. Throw an exception.
        if args:
            raise exceptions.InvalidOptions('Must pass named-args (kwargs)')

        ret = yield self._client.fetch(
            url='%s%s' % (self._ENDPOINT, self._path),
            method=http_method.upper(),
            params=kwargs,
            auth_username=self._CONFIG.get('auth', {}).get('user'),
            auth_password=self._CONFIG.get('auth', {}).get('pass'))
        raise gen.Return(ret)
Beispiel #13
0
    def _delete_mci_reference(self, map_obj):
        self.log.info('Deleting MCI %s from ServerTemplate' %
                      map_obj.links['multi_cloud_image'])

        try:
            yield self._client.destroy_resource(map_obj)
        except requests.exceptions.HTTPError as e:
            if 'Default ServerTemplateMultiCloudImages' in e.response.text:
                raise exceptions.InvalidOptions(
                    'Cannot delete the current default MultiCloudImage '
                    'for this ServerTemplate. You must first re-assign a '
                    'new default image.')
        self.changed = True
Beispiel #14
0
    def _validate_options(self):
        """Validate that all the required options were passed in.

        Args:
            options: A dictionary of options.

        Raises:
            exceptions.InvalidOptions
        """

        # Loop through all_options, and find the required ones
        required = [
            opt_name for (opt_name, definition) in self.all_options.items()
            if definition[1] is REQUIRED
        ]

        self.log.debug('Checking for required options: %s' % required)
        option_errors = []
        option_warnings = []
        for opt in required:
            if opt not in self._options:
                description = self.all_options[opt][2]
                option_errors.append('Option "%s" is required: %s' %
                                     (opt, description))

        for opt, value in self._options.items():
            if opt not in self.all_options:
                option_warnings.append('Option "%s" is not expected by %s.' %
                                       (opt, self.__class__.__name__))
                continue

            expected_type = self.all_options[opt][0]

            # Unicode is not a `str` but it is a `basestring`
            # Cast the passed value explicitly as a string
            if isinstance(value, basestring):
                value = str(value)

            if not (value is None or isinstance(value, expected_type)):
                message = 'Option "%s" has to be %s and is %s.' % (
                    opt, expected_type, type(value))
                option_errors.append(message)

        for w in option_warnings:
            self.log.warning(w)

        if option_errors:
            for e in option_errors:
                self.log.critical(e)
            raise exceptions.InvalidOptions(
                'Found %s issue(s) with passed options.' % len(option_errors))
Beispiel #15
0
    def readfile(self, path):
        """Return file contents as a string.

        Raises:
            InvalidOptions if file is not found, or readable.
        """

        try:
            with open(path) as f:
                contents = f.read()
        except IOError as e:
            raise exceptions.InvalidOptions(e)

        return contents
Beispiel #16
0
    def __init__(self, *args, **kwargs):
        """Validate the user-supplied parameters at instantiation time."""
        super(Create, self).__init__(*args, **kwargs)
        # By default, we're strict on our array array validation
        self._array_raise_on = 'notfound'
        self._array_allow_mock = False

        if not self.option('strict_array'):
            self._array_raise_on = None
            self._array_allow_mock = True

        if self.option('vote_type') not in ('grow', 'shrink', None):
            raise exceptions.InvalidOptions(
                'vote_type must be either: grow, shrink, None')
Beispiel #17
0
    def _execute(self):

        dep = yield self._find_deployment(self.option('name'))
        if not dep:
            raise exceptions.InvalidOptions(
                'Deployment "%s" does not exist.' % self.option('name'))

        info = (yield self._client.show(dep.self)).soul

        if self._dry:
            self.log.info('Would delete deployment %s' % info['name'])
            raise gen.Return()

        self.log.info('Deleting deployment %s' % info['name'])
        yield self._client.destroy_resource(dep)
Beispiel #18
0
    def _verify_one_default_image(self):
        """Parses through the supplied images and finds the default one.

        If there are more than one default image defined, throws an exception.

        args:
            images: ServerTemplateMultiCloudImages object
        """
        default_image = [
            i['mci'] for i in self.option('images') if i.get('is_default')
        ]

        if len(default_image) > 1:
            raise exceptions.InvalidOptions(
                'Only one image may have is_default set: %s' %
                ', '.join(default_image))
Beispiel #19
0
    def _parse_group_config(self):
        """Parses the ElastiGroup config and replaces tokens.

        Reads through the supplied ElastiGroup configuration JSON blob (or
        YAML!), replaces any tokens that need replacement, and then sanity
        checks it against our schema.

        Note, contextual tokens (which are evaluated at run time, not
        compilation time) are not included here. Instead, those will be
        evaluated in the self._precache() method.
        """
        config = self.option('config')

        if config is None:
            return None

        self.log.debug('Parsing and validating %s' % config)

        # Join the init_tokens the class was instantiated with and the explicit
        # tokens that the user supplied.
        tokens = dict(self._init_tokens)
        tokens.update(self.option('tokens'))

        try:
            parsed = utils.convert_script_to_dict(script_file=config,
                                                  tokens=tokens)
        except (kingpin_exceptions.InvalidScript, LookupError) as e:
            raise exceptions.InvalidOptions('Error parsing %s: %s' %
                                            (config, e))

        # The userData portion of the body data needs to be Base64 encoded if
        # its not already. We will try to decode whatever is there, and if it
        # fails, we assume its raw text and we encode it.
        orig_data = (
            parsed['group']['compute']['launchSpecification']['userData'])
        new = base64.b64encode(orig_data)
        parsed['group']['compute']['launchSpecification']['userData'] = new

        # Ensure that the name of the ElastiGroup in the config file matches
        # the name that was supplied to the actor -- or overwrite it.
        parsed['group']['name'] = self.option('name')

        # Now run the configuration through the schema validator
        ElastiGroupSchema.validate(parsed)

        return parsed
Beispiel #20
0
    def __init__(self, *args, **kwargs):
        super(ElastiGroup, self).__init__(*args, **kwargs)

        # Quickly make sure that the roll_batch_size and roll_grace_period are
        # integers...
        for key in ('roll_batch_size', 'roll_grace_period'):
            try:
                self._options[key] = int(self._options[key])
            except ValueError:
                raise exceptions.InvalidOptions('%s (%s) must be an integer' %
                                                (key, self._options[key]))

        # Parse the user-supplied ElastiGroup config, swap in any tokens, etc.
        self._config = self._parse_group_config()

        # Filld in later by self._precache()
        self._group = None
Beispiel #21
0
    def _get_check(self):
        """Get check data for actor's option "name".

        Pingdom returns an array of all checks. This method finds the check
        with the exact name and returns its contents.

        Raises InvalidOptions if the check does not exist.
        """
        resp = yield self._pingdom_client.checks().http_get()
        all_checks = resp['checks']
        check = [c for c in all_checks if c['name'] == self.option('name')]

        if not check:
            raise exceptions.InvalidOptions('Check name "%s" was not found.' %
                                            self.option('name'))

        raise gen.Return(check[0])
Beispiel #22
0
    def _get_mci_href(self, image):
        name = image['mci']
        revision = image.get('revision', 0)
        default = image.get('is_default', False)

        mci = yield self._client.find_by_name_and_keys(
            collection=self._client._client.multi_cloud_images,
            name=name,
            revision=revision)

        if not mci:
            raise exceptions.InvalidOptions(
                'Invalid MCI Name/Rev supplied: %s/%s' % (name, revision))

        self.desired_images[mci.href] = {
            'default': default,
        }
        self.log.debug('Discovered %s (%s) -> %s' % (name, revision, mci.href))
Beispiel #23
0
    def _load_service_definition(
            service_definition_file, tokens, default_tokens={}):
        """Loads and verifies a service definition template file, and
        interpolates tokens. and optionally default tokens which may contain
        environment variables. The service definition template file
        can be None.

        Args:
            service_definition_file: service definition file to load.
                If None or an empty string, this returns only defaults.
            tokens: dict of key/value pairs to interpolate into the file.
            default_tokens: dict of default key/value pairs to merge
                with tokens

        Returns:
            Resulting service definition dict.
        """
        if not service_definition_file:
            service_definition = {}
        else:
            final_tokens = default_tokens.copy()
            final_tokens.update(tokens)

            service_definition = utils.convert_script_to_dict(
                service_definition_file, final_tokens)
            try:
                jsonschema.validate(service_definition,
                                    SERVICE_DEFINITION_SCHEMA)

            except jsonschema.exceptions.ValidationError as e:
                raise exceptions.InvalidOptions(e)

        # Set default values.
        service_definition.setdefault(
            'loadBalancers', [])
        service_definition.setdefault(
            'deploymentConfiguration', {})
        return service_definition
Beispiel #24
0
    def _get_group(self):
        """Finds and returns the existing ElastiGroup configuration.

        If the ElastiGroup exists, it returns the configuration for the group.
        If the group is missing, it returns None. Used by the self._precache()
        method to determine whether or not the desired ElastiGroup already
        exists or not, and what its configuration looks like.

        Returns:
          A dictionary with the ElastiGroup configuration returned by Spotinst
          or None if no matching group is found.

        Raises:
          exceptions.InvalidOptions: If too many groups are returned.
        """
        all_groups = yield self._list_groups()
        if not all_groups:
            raise gen.Return(None)

        matching = [
            group for group in all_groups
            if group['name'] == self.option('name')
        ]

        if len(matching) > 1:
            raise exceptions.InvalidOptions(
                'Found more than one ElastiGroup with the name %s - '
                'this actor cannot manage multiple groups with the same'
                'name, you must use a unique name for each group.' %
                self.option('name'))

        if len(matching) < 1:
            self.log.debug('Did not find an existing ElastiGroup')
            raise gen.Return(None)

        match = matching[0]
        self.log.debug('Found ElastiGroup %s' % match['id'])
        raise gen.Return({'group': match})
Beispiel #25
0
    def __init__(self, *args, **kwargs):
        """Initializes all of the sub actors.

        By actually initializing all of the Actors supplied to us during the
        __init__, we effectively do a full instantiation of every Actor defined
        in the supplied JSON all at once and upfront long before we try to
        execute any code. This greatly increases our chances of catching JSON
        errors because every single object is pre-initialized before we ever
        begin executing any of our steps.
        """
        super(BaseGroupActor, self).__init__(*args, **kwargs)

        if type(self.option('contexts')) == dict:
            try:
                filename = self.option('contexts').get('file', '')
                open(filename)
            except IOError as e:
                self.log.error('Option `contexts` must have valid `file`. '
                               'Received: %s' % filename)
                raise exceptions.InvalidOptions(e)

        # Pre-initialize all of our actions!
        self._actions = self._build_actions()
Beispiel #26
0
    def str2bool(self, v, strict=False):
        """Returns a Boolean from a variety of inputs.

        args:
            value: String/Bool
            strict: Whether or not to _only_ convert the known words into
            booleans, or whether to allow "any" word to be considered True
            other than the known False words.

        returns:
            A boolean
        """
        false = ('no', 'false', 'f', '0')
        true = ('yes', 'true', 't', '1')

        string = str(v).lower()

        if strict:
            if string not in true and string not in false:
                raise exceptions.InvalidOptions(
                    'Expected [%s, %s] but got: %s' % (true, false, string))

        return string not in false
Beispiel #27
0
    def __init__(self, *args, **kwargs):
        """Check for required settings."""

        super(AWSBaseActor, self).__init__(*args, **kwargs)

        # By default, we will try to let Boto handle discovering its
        # credentials at instantiation time. This _can_ result in synchronous
        # API calls to the Metadata service, but those should be fast.
        key = None
        secret = None

        # In the event though that someone has explicitly set the AWS access
        # keys in the environment (either for the purposes of a unit test, or
        # because they wanted to), we use those values.
        if (aws_settings.AWS_ACCESS_KEY_ID
                and aws_settings.AWS_SECRET_ACCESS_KEY):
            key = aws_settings.AWS_ACCESS_KEY_ID
            secret = aws_settings.AWS_SECRET_ACCESS_KEY

        # On our first simple IAM connection, test the credentials and make
        # sure things worked!
        try:
            # Establish connection objects that don't require a region
            self.iam_conn = boto.iam.connection.IAMConnection(
                aws_access_key_id=key, aws_secret_access_key=secret)
        except boto.exception.NoAuthHandlerFound:
            raise exceptions.InvalidCredentials(
                'AWS settings imported but not all credentials are supplied. '
                'AWS_ACCESS_KEY_ID: %s, AWS_SECRET_ACCESS_KEY: %s' %
                (aws_settings.AWS_ACCESS_KEY_ID,
                 aws_settings.AWS_SECRET_ACCESS_KEY))

        # Establish region-specific connection objects.
        region = self.option('region')
        if not region:
            return

        # In case a zone was provided instead of region we can convert
        # it on the fly
        zone_check = re.match(r'(.*[0-9])([a-z]*)$', region)

        if zone_check and zone_check.group(2):
            zone = region  # Only saving this for the log below

            # Set the fixed region
            region = zone_check.group(1)
            self.log.warning('Converting zone "%s" to region "%s".' %
                             (zone, region))

        region_names = [r.name for r in boto.ec2.elb.regions()]
        if region not in region_names:
            err = ('Region "%s" not found. Available regions: %s' %
                   (region, region_names))
            raise exceptions.InvalidOptions(err)

        self.ec2_conn = boto.ec2.connect_to_region(
            region, aws_access_key_id=key, aws_secret_access_key=secret)
        self.ecs_conn = boto3.client('ecs',
                                     region_name=region,
                                     aws_access_key_id=key,
                                     aws_secret_access_key=secret)
        self.elb_conn = boto.ec2.elb.connect_to_region(
            region, aws_access_key_id=key, aws_secret_access_key=secret)
        self.elbv2_conn = boto3.client('elbv2',
                                       region_name=region,
                                       aws_access_key_id=key,
                                       aws_secret_access_key=secret)
        self.cf3_conn = boto3.client('cloudformation',
                                     region_name=region,
                                     aws_access_key_id=key,
                                     aws_secret_access_key=secret)
        self.sqs_conn = boto.sqs.connect_to_region(
            region, aws_access_key_id=key, aws_secret_access_key=secret)
        self.s3_conn = boto3.client('s3',
                                    region_name=region,
                                    aws_access_key_id=key,
                                    aws_secret_access_key=secret)
Beispiel #28
0
    def _ensure_mci_default(self):
        """Sets the ServerTemplates Default MCI.

        After we've attached all of the MCIs to a ServerTemplate, we need to
        (optionally) set the "default" MCI to use if the user doesn't select
        one. This default can only be set after these attachments are made.
        Also, the default choice is then un-deletable from the MCI until a new
        default is selected.
        """
        # If there is no current default, then that means there are no MCIs
        # associated at all and we can't do anything. This only happens when
        # you're creating a new MCI from scratch.
        if 'default_multi_cloud_image' not in self.st.links:
            raise gen.Return()

        # Get the default MCI href as described by the user -- or just get the
        # first key in the list and treat that as the desired default.
        try:
            default_mci_href = [
                key for key in self.desired_images.keys()
                if self.desired_images[key]['default'] is True
            ][0]
        except IndexError:
            default_mci_href = self.desired_images.keys()[0]

        # Compare the desired vs current default_multi_cloud_image_href. This
        # comparison is quick and doesn't require any API calls, so we do it
        # before we do anything else.
        current_default = self.st.links['default_multi_cloud_image']
        self.log.debug('Desired Default MCI HREF: %s' % default_mci_href)
        self.log.debug('Current Default MCI HREF: %s' % current_default)
        matching = (current_default == default_mci_href)

        # They match? Great, get out of here before we do anything bad!
        if matching:
            self.log.debug('Existing Default MCI HREF matches')
            raise gen.Return()

        # At this point, we know they don't match .. so we need to go off and
        # get all of the ServerTemplateMultiCloudImage references that link
        # these ServerTemplates to the MCIs.
        mci_refs = yield self._client.find_by_name_and_keys(
            collection=self._client._client.server_template_multi_cloud_images,
            server_template_href=self.st.href)

        # Final sanity check here -- if we're in DRY mode, get out!
        if self._dry:
            self.log.warning('Would have updated default MCI HREF to: %s' %
                             default_mci_href)
            raise gen.Return()

        # If we're not in DRY mode, AND we're updating the MCI reference to a
        # new default, then find the appropriate
        # server_template_muilti_cloud_image reference object. This should
        # always work, but some slowness in RightScales APIs has demonstrated
        # problems in the past, so we wrap this in a Try/Except block.
        try:
            new_default = [
                s for s in mci_refs
                if s.links['multi_cloud_image'] == default_mci_href
            ][0]
        except IndexError:
            raise exceptions.InvalidOptions(
                'Unable to find the desired MultiCloud image '
                'attached to the ServerTemplate. This should never '
                'happen -- but can happen if the RightScale API is '
                'returning stale data. In that case, please wait and try '
                'again in the future.')

        # Finally, we have the desired new_default object... grab its URL
        # reference and lets make the API call.
        self.log.info('Making %s the default MCI' % default_mci_href)
        url = '%s/make_default' % new_default.links['self']
        yield self._client.make_generic_request(url, post=[])
        self.changed = True
Beispiel #29
0
 def validate(self, option):
     try:
         jsonschema.Draft4Validator(self.SCHEMA).validate(option)
     except jsonschema.exceptions.ValidationError as e:
         raise exceptions.InvalidOptions(
             'Supplied parameter does not match schema: %s' % e)
Beispiel #30
0
    def _validate_options(self):
        """Validate that all the required options were passed in.

        Args:
            options: A dictionary of options.

        Raises:
            exceptions.InvalidOptions
        """

        # Loop through all_options, and find the required ones
        required = [
            opt_name for (opt_name, definition) in self.all_options.items()
            if definition[1] is REQUIRED
        ]

        self.log.debug('Checking for required options: %s' % required)
        option_errors = []
        option_warnings = []
        for opt in required:
            if opt not in self._options:
                description = self.all_options[opt][2]
                option_errors.append('Option "%s" is required: %s' %
                                     (opt, description))

        for opt, value in self._options.items():
            if opt not in self.all_options:
                option_warnings.append('Option "%s" is not expected by %s.' %
                                       (opt, self.__class__.__name__))
                continue

            expected_type = self.all_options[opt][0]

            # Unicode is not a `str` but it is a `basestring`
            # Cast the passed value explicitly as a string
            if isinstance(value, basestring):
                value = str(value)

            # If the expected_type has an attribute 'valid', then verify that
            # the option passed in is one of those valid options.
            if hasattr(expected_type, 'validate'):
                try:
                    expected_type.validate(value)
                    continue
                except exceptions.InvalidOptions as e:
                    option_errors.append(e)

            # If the option type is Bool, try to convert the strings True/False
            # into booleans. If this doesn't work, siletly move on and let the
            # failure get caught below.
            if expected_type is bool:
                try:
                    value = self.str2bool(value, strict=True)
                    self._options[opt] = value
                except exceptions.InvalidOptions as e:
                    self.log.warning(e)

            if not (value is None or isinstance(value, expected_type)):
                message = 'Option "%s" has to be %s and is %s.' % (
                    opt, expected_type, type(value))
                option_errors.append(message)

        for w in option_warnings:
            self.log.warning(w)

        if option_errors:
            for e in option_errors:
                self.log.critical(e)
            raise exceptions.InvalidOptions(
                'Found %s issue(s) with passed options.' % len(option_errors))