Beispiel #1
0
def string_to_dict(var_string, allow_kv=True):
    """Returns a dictionary given a string with yaml or json syntax.
    If data is not present in a key: value format, then it return
    an empty dictionary.

    Attempts processing string by 3 different methods in order:
        1. as JSON      2. as YAML      3. as custom key=value syntax
    Throws an error if all of these fail in the standard ways."""
    # try:
    #     # Accept all valid "key":value types of json
    #     return_dict = json.loads(var_string)
    #     assert type(return_dict) is dict
    # except (TypeError, AttributeError, ValueError, AssertionError):
    try:
        # Accept all JSON and YAML
        return_dict = yaml.load(var_string)
        assert type(return_dict) is dict
    except (AttributeError, yaml.YAMLError, AssertionError):
        # if these fail, parse by key=value syntax
        try:
            assert allow_kv
            return_dict = parse_kv(var_string)
        except:
            raise exc.TowerCLIError(
                'failed to parse some of the extra '
                'variables.\nvariables: \n%s' % var_string
            )
    return return_dict
Beispiel #2
0
 def set_base_url(self, user, team):
     """Assure that endpoint is nested under a user or team"""
     if self.no_lookup_flag:
         return
     if user:
         self.endpoint = '/users/%d/permissions/' % user
     elif team:
         self.endpoint = '/teams/%d/permissions/' % team
     else:
         raise exc.TowerCLIError('Specify either a user or a team.')
     self.no_lookup_flag = True
Beispiel #3
0
 def prefix(self):
     """Return the appropriate URL prefix to prepend to requests,
     based on the host provided in settings.
     """
     host = settings.host
     if '://' not in host:
         host = 'https://%s' % host.strip('/')
     elif host.startswith('http://') and settings.verify_ssl:
         raise exc.TowerCLIError(
             'Can not verify ssl with non-https protocol. Change the '
             'verify_ssl configuration setting to continue.')
     return '%s/api/v1/' % host.rstrip('/')
Beispiel #4
0
    def create(self, fail_on_found=False, force_on_exists=False, **kwargs):
        """Create a notification template.

        All required configuration-related fields (required according to
        notification_type) must be provided.

        There are two types of notification template creation: isolatedly
        creating a new notification template and creating a new notification
        template under a job template. Here the two types are discriminated by
        whether to provide --job-template option. --status option controls
        more specific, job-run-status-related association.

        Fields in the resource's `identity` tuple are used for a lookup;
        if a match is found, then no-op (unless `force_on_exists` is set) but
        do not fail (unless `fail_on_found` is set).
        """
        config_item = self._separate(kwargs)
        jt_id = kwargs.pop('job_template', None)
        status = kwargs.pop('status', 'any')
        old_endpoint = self.endpoint
        if jt_id is not None:
            jt = get_resource('job_template')
            jt.get(pk=jt_id)
            try:
                nt_id = self.get(**copy.deepcopy(kwargs))['id']
            except exc.NotFound:
                pass
            else:
                if fail_on_found:
                    raise exc.TowerCLIError('Notification template already '
                                            'exists and fail-on-found is '
                                            'switched on. Please use'
                                            ' "associate_notification" method'
                                            ' of job_template instead.')
                else:
                    debug.log(
                        'Notification template already exists, '
                        'associating with job template.',
                        header='details')
                    return jt.associate_notification(jt_id,
                                                     nt_id,
                                                     status=status)
            self.endpoint = '/job_templates/%d/notification_templates_%s/' %\
                            (jt_id, status)
        self._configuration(kwargs, config_item)
        result = super(Resource, self).create(**kwargs)
        self.endpoint = old_endpoint
        return result
Beispiel #5
0
 def _separate(self, kwargs):
     """Remove None-valued and configuration-related keyworded arguments
     """
     self._pop_none(kwargs)
     result = {}
     for field in Resource.config_fields:
         if field in kwargs:
             result[field] = kwargs.pop(field)
             if field in Resource.json_fields:
                 try:
                     data = json.loads(result[field])
                     result[field] = data
                 except ValueError:
                     raise exc.TowerCLIError('Provided json file format '
                                             'invalid. Please recheck.')
     return result
Beispiel #6
0
    def cancel(self, pk, fail_if_not_running=False):
        """Cancel a currently running job.

        Fails with a non-zero exit status if the job cannot be canceled.
        """
        # Attempt to cancel the job.
        try:
            client.post('/jobs/%d/cancel/' % pk)
            changed = True
        except exc.MethodNotAllowed:
            changed = False
            if fail_if_not_running:
                raise exc.TowerCLIError('Job not running.')

        # Return a success.
        return adict({'status': 'canceled', 'changed': changed})
Beispiel #7
0
 def _configuration(self, kwargs, config_item):
     """Combine configuration-related keyworded arguments into
     notification_configuration.
     """
     if 'notification_configuration' not in config_item:
         if 'notification_type' not in kwargs:
             return
         nc = kwargs['notification_configuration'] = {}
         for field in Resource.configuration[kwargs['notification_type']]:
             if field not in config_item:
                 raise exc.TowerCLIError('Required config field %s not'
                                         ' provided.' % field)
             else:
                 nc[field] = config_item[field]
     else:
         kwargs['notification_configuration'] = \
                 config_item['notification_configuration']
Beispiel #8
0
    def launch(self,
               monitor=False,
               wait=False,
               timeout=None,
               become=False,
               **kwargs):
        """Launch a new ad-hoc command.

        Runs a user-defined command from Ansible Tower, immediately starts it,
        and returns back an ID in order for its status to be monitored.
        """
        # This feature only exists for versions 2.2 and up
        r = client.get('/')
        if 'ad_hoc_commands' not in r.json():
            raise exc.TowerCLIError('Your host is running an outdated version'
                                    'of Ansible Tower that can not run '
                                    'ad-hoc commands (2.2 or earlier)')

        # Pop the None arguments because we have no .write() method in
        # inheritance chain for this type of resource. This is needed
        self._pop_none(kwargs)

        # Change the flag to the dictionary format
        if become:
            kwargs['become_enabled'] = True

        # Actually start the command.
        debug.log('Launching the ad-hoc command.', header='details')
        result = client.post(self.endpoint, data=kwargs)
        command = result.json()
        command_id = command['id']

        # If we were told to monitor the command once it started, then call
        # monitor from here.
        if monitor:
            return self.monitor(command_id, timeout=timeout)
        elif wait:
            return self.wait(command_id, timeout=timeout)

        # Return the command ID and other response data
        answer = OrderedDict((
            ('changed', True),
            ('id', command_id),
        ))
        answer.update(result.json())
        return answer
Beispiel #9
0
    def create(self, fail_on_found=False, force_on_exists=False, **kwargs):
        """Create a new label.

        There are two types of label creation: isolatedly creating a new
        label and creating a new label under a job template. Here the two
        types are discriminated by whether to provide --job-template option.

        Fields in the resource's `identity` tuple are used for a lookup;
        if a match is found, then no-op (unless `force_on_exists` is set) but
        do not fail (unless `fail_on_found` is set).
        """
        jt_id = kwargs.pop('job_template', None)
        old_endpoint = self.endpoint
        if jt_id is not None:
            jt = get_resource('job_template')
            jt.get(pk=jt_id)
            try:
                label_id = self.get(name=kwargs.get('name', None),
                                    organization=kwargs.get(
                                        'organization', None))['id']
            except exc.NotFound:
                pass
            else:
                if fail_on_found:
                    raise exc.TowerCLIError('Label already exists and fail-on'
                                            '-found is switched on. Please use'
                                            ' "associate_label" method of job'
                                            '_template instead.')
                else:
                    debug.log(
                        'Label already exists, associating with job '
                        'template.',
                        header='details')
                    return jt.associate_label(jt_id, label_id)
            self.endpoint = '/job_templates/%d/labels/' % jt_id
        result = super(Resource, self).\
            create(fail_on_found=fail_on_found,
                   force_on_exists=force_on_exists, **kwargs)
        self.endpoint = old_endpoint
        return result
Beispiel #10
0
    def cancel(self, pk=None, fail_if_not_running=False, **kwargs):
        """Cancel a currently running job.

        Fails with a non-zero exit status if the job cannot be canceled.
        You must provide either a pk or parameters in the job's identity.
        """
        # Search for the record if pk not given
        if not pk:
            existing_data = self.get(**kwargs)
            pk = existing_data['id']

        cancel_endpoint = '%s%d/cancel/' % (self.endpoint, pk)
        # Attempt to cancel the job.
        try:
            client.post(cancel_endpoint)
            changed = True
        except exc.MethodNotAllowed:
            changed = False
            if fail_if_not_running:
                raise exc.TowerCLIError('Job not running.')

        # Return a success.
        return {'status': 'canceled', 'changed': changed}
Beispiel #11
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.')