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
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
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.')
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)
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
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.')