Exemplo n.º 1
0
    def create(self, fail_on_found=False, force_on_exists=False, **kwargs):
        """Create a group and, if necessary, modify the inventory source within
        the group.
        """
        group_fields = [f.name for f in self.fields]
        if kwargs.get('parent', None):
            parent_data = self.set_child_endpoint(parent=kwargs['parent'],
                                                  inventory=kwargs.get(
                                                      'inventory', None))
            kwargs['inventory'] = parent_data['inventory']
            group_fields.append('group')
        elif 'inventory' not in kwargs:
            raise exc.UsageError('To create a group, you must provide a '
                                 'parent inventory or parent group.')

        # Break out the options for the group vs its inventory_source
        is_kwargs = {}
        for field in kwargs.copy():
            if field not in group_fields:
                if field == 'job_timeout':
                    is_kwargs['timeout'] = kwargs.pop(field)
                else:
                    is_kwargs[field] = kwargs.pop(field)

        # Handle alias for "manual" source
        if is_kwargs.get('source', None) == 'manual':
            is_kwargs.pop('source')

        # First, create the group.
        answer = super(Resource, self).create(fail_on_found=fail_on_found,
                                              force_on_exists=force_on_exists,
                                              **kwargs)

        # If the group already exists and we aren't supposed to make changes,
        # then we're done.
        if not force_on_exists and not answer['changed']:
            return answer

        # Sanity check: A group was created, but do we need to do anything
        # with the inventory source at all? If no credential or source
        # was specified, then we'd just be updating the inventory source
        # with an effective no-op.
        if len(is_kwargs) == 0:
            return answer

        # Get the inventory source ID ("isid").
        # Inventory sources are not created directly; rather, one was created
        # automatically when the group was created.
        isid = self._get_inventory_source_id(answer)

        # We now have our inventory source ID; modify it according to the
        # provided parameters.
        isrc = get_resource('inventory_source')
        is_answer = isrc.write(pk=isid, force_on_exists=True, **is_kwargs)

        # If either the inventory_source or the group objects were modified
        # then refelect this in the output to avoid confusing the user.
        if is_answer['changed']:
            answer['changed'] = True
        return answer
Exemplo n.º 2
0
    def obj_res(data, fail_on=['type', 'obj', 'res']):
        """
        Given some CLI input data,
        Returns the following and their types:
        obj - the role grantee
        res - the resource that the role applies to
        """
        errors = []
        if not data.get('type', None) and 'type' in fail_on:
            errors += ['You must provide a role type to use this command.']

        # Find the grantee, and remove them from resource_list
        obj = None
        obj_type = None
        for fd in ACTOR_FIELDS:
            if data.get(fd, False):
                if not obj:
                    obj = data[fd]
                    obj_type = fd
                else:
                    errors += [
                        'You can not give a role to a user '
                        'and team at the same time.'
                    ]
                    break
        if not obj and 'obj' in fail_on:
            errors += [
                'You must specify either user or '
                'team to use this command.'
            ]

        # Out of the resource list, pick out available valid resource field
        res = None
        res_type = None
        for fd in RESOURCE_FIELDS:
            if data.get(fd, False):
                if not res:
                    res = data[fd]
                    res_type = fd
                    if res_type == 'target_team':
                        res_type = 'team'
                else:
                    errors += [
                        'You can only give a role to one '
                        'type of resource at a time.'
                    ]
                    break
        if not res and 'res' in fail_on:
            errors += [
                'You must specify a target resource '
                'to use this command.'
            ]

        if errors:
            raise exc.UsageError("\n".join(errors))
        return obj, obj_type, res, res_type
Exemplo n.º 3
0
 def helper(kwargs, obj):
     """The helper function preceding actual function that aggregates
     unified jt fields.
     """
     unified_job_template = None
     for item in UNIFIED_JT:
         if kwargs.get(item, None) is not None:
             jt_id = kwargs.pop(item)
             if unified_job_template is None:
                 unified_job_template = (item, jt_id)
             else:
                 raise exc.UsageError(
                     'More than one unified job template fields provided, '
                     'please tighten your criteria.')
     if unified_job_template is not None:
         kwargs['unified_job_template'] = unified_job_template[1]
         obj.identity = tuple(list(obj.identity) + ['unified_job_template'])
         return '/'.join([
             UNIFIED_JT[unified_job_template[0]],
             str(unified_job_template[1]), 'schedules/'
         ])
     elif is_create:
         raise exc.UsageError('You must provide exactly one unified job'
                              ' template field during creation.')
Exemplo n.º 4
0
    def list(self, root=False, **kwargs):
        """Return a list of groups."""

        # Sanity check: If we got `--root` and no inventory, that's an
        # error.
        if root and not kwargs.get('inventory', None):
            raise exc.UsageError('The --root option requires specifying an '
                                 'inventory also.')

        # If we are tasked with getting root groups, do that.
        if root:
            inventory_id = kwargs['inventory']
            r = client.get('/inventories/%d/root_groups/' % inventory_id)
            return r.json()

        # Return the superclass implementation.
        return super(Resource, self).list(**kwargs)
Exemplo n.º 5
0
            def get_command(self, ctx, name):
                """Retrieve the appropriate method from the Resource,
                decorate it as a click command, and return that method.
                """
                # Sanity check: Does a method exist corresponding to this
                # command? If not, this is an error.
                if not hasattr(self.resource, name):
                    raise exc.UsageError(
                        'The %s resource has no such command: "%s"' %
                        (self.resource_name, name), )

                # Get the method.
                method = getattr(self.resource, name)

                # Get any attributes that were given at command-declaration
                # time.
                attrs = getattr(method, '_cli_command_attrs', {})

                # If the help message comes from the docstring, then
                # convert it into a message specifically for this resource.
                help_text = inspect.getdoc(method)
                attrs['help'] = self._auto_help_text(help_text or '')

                # On some methods, we ignore the defaults, which are intended
                # for writing and not reading; process this.
                ignore_defaults = attrs.pop('ignore_defaults', False)

                # Wrap the method, such that it outputs its final return
                # value rather than returning it.
                new_method = self._echo_method(method)

                # Soft copy the "__click_params__", if any exist.
                # This is the internal holding method that the click library
                # uses to store @click.option and @click.argument directives
                # before the method is converted into a command.
                #
                # Because self._echo_method uses @functools.wraps, this is
                # actually preserved; the purpose of copying it over is
                # so we can get our resource fields at the top of the help;
                # the easiest way to do this is to load them in before the
                # conversion takes place. (This is a happy result of Armin's
                # work to get around Python's processing decorators
                # bottom-to-top.)
                click_params = getattr(method, '__click_params__', [])
                new_method.__click_params__ = copy(click_params)

                # Write options based on the fields available on this resource.
                if attrs.pop('use_fields_as_options', True):
                    for field in reversed(self.resource.fields):
                        if not field.is_option:
                            continue

                        # Create the initial arguments based on the
                        # option value. If we have a different key to use
                        # (which is what gets routed to the Tower API),
                        # ensure that is the first argument.
                        args = [field.option]
                        if field.key:
                            args.insert(0, field.key)

                        # Apply the option to the method.
                        click.option(
                            *args,
                            default=field.default
                            if not ignore_defaults else None,
                            help=field.help,
                            type=field.type,
                            show_default=field.show_default)(new_method)

                # Make a click Command instance using this method
                # as the callback, and return it.
                cmd = command(name=name, cls=Command, **attrs)(new_method)

                # If this method has a `pk` positional argument,
                # then add a click argument for it.
                code = six.get_function_code(method)
                if 'pk' in code.co_varnames:
                    click.argument('pk',
                                   nargs=1,
                                   required=False,
                                   type=int,
                                   metavar='[ID]')(cmd)

                # Done; return the command.
                return cmd
Exemplo n.º 6
0
def config(key=None, value=None, scope='user', global_=False, unset=False):
    """Read or write tower-cli configuration.

    `tower config` saves the given setting to the appropriate Tower CLI;
    either the user's ~/.tower_cli.cfg file, or the /etc/tower/tower_cli.cfg
    file if --global is used.

    Writing to /etc/tower/tower_cli.cfg is likely to require heightened
    permissions (in other words, sudo).
    """
    # If the old-style `global_` option is set, issue a deprecation notice.
    if global_:
        scope = 'global'
        warnings.warn(
            'The `--global` option is deprecated and will be '
            'removed. Use `--scope=global` to get the same effect.',
            DeprecationWarning)

    # If no key was provided, print out the current configuration
    # in play.
    if not key:
        seen = set()
        parser_desc = {
            'runtime':
            'Runtime options.',
            'local':
            'Local options (set with `tower-cli config '
            '--scope=local`; stored in .tower_cli.cfg of this '
            'directory or a parent)',
            'user':
            '******'
            '~/.tower_cli.cfg).',
            'global':
            'Global options (set with `tower-cli config '
            '--scope=global`, stored in /etc/tower/tower_cli.cfg).',
            'defaults':
            'Defaults.',
        }

        # Iterate over each parser (English: location we can get settings from)
        # and print any settings that we haven't already seen.
        #
        # We iterate over settings from highest precedence to lowest, so any
        # seen settings are overridden by the version we iterated over already.
        click.echo('')
        for name, parser in zip(settings._parser_names, settings._parsers):
            # Determine if we're going to see any options in this
            # parser that get echoed.
            will_echo = False
            for option in parser.options('general'):
                if option in seen:
                    continue
                will_echo = True

            # Print a segment header
            if will_echo:
                secho('# %s' % parser_desc[name], fg='green', bold=True)

            # Iterate over each option in the parser and, if we haven't
            # already seen an option at higher precedence, print it.
            for option in parser.options('general'):
                if option in seen:
                    continue
                echo_setting(option)
                seen.add(option)

            # Print a nice newline, for formatting.
            if will_echo:
                click.echo('')
        return

    # Sanity check: Is this a valid configuration option? If it's not
    # a key we recognize, abort.
    if not hasattr(settings, key):
        raise exc.TowerCLIError('Invalid configuration option "%s".' % key)

    # Sanity check: The combination of a value and --unset makes no
    # sense.
    if value and unset:
        raise exc.UsageError('Cannot provide both a value and --unset.')

    # If a key was provided but no value was provided, then just
    # print the current value for that key.
    if key and not value and not unset:
        echo_setting(key)
        return

    # Okay, so we're *writing* a key. Let's do this.
    # First, we need the appropriate file.
    filename = os.path.expanduser('~/.tower_cli.cfg')
    if scope == 'global':
        if not os.path.isdir('/etc/tower/'):
            raise exc.TowerCLIError('/etc/tower/ does not exist, and this '
                                    'command cowardly declines to create it.')
        filename = '/etc/tower/tower_cli.cfg'
    elif scope == 'local':
        filename = '.tower_cli.cfg'

    # Read in the appropriate config file, write this value, and save
    # the result back to the file.
    parser = Parser()
    parser.add_section('general')
    parser.read(filename)
    if unset:
        parser.remove_option('general', key)
    else:
        parser.set('general', key, value)
    with open(filename, 'w') as config_file:
        parser.write(config_file)

    # Give rw permissions to user only fix for issue number 48
    try:
        os.chmod(filename, stat.S_IRUSR | stat.S_IWUSR)
    except Exception as e:
        warnings.warn(
            'Unable to set permissions on {0} - {1} '.format(filename, e),
            UserWarning)
    click.echo('Configuration updated successfully.')