Example #1
0
        def func(*args, **kwargs):
            # Echo warning if this method is deprecated.
            if getattr(method, 'deprecated', False):
                debug.log('This method is deprecated in Tower 3.0.',
                          header='warning')

            result = method(*args, **kwargs)

            # If this was a request that could result in a modification
            # of data, print it in Ansible coloring.
            color_info = {}
            if isinstance(result, dict) and 'changed' in result:
                if result['changed']:
                    color_info['fg'] = 'yellow'
                else:
                    color_info['fg'] = 'green'

            # Piece together the result into the proper format.
            format = getattr(
                self, '_format_%s' %
                (getattr(method, 'format_freezer', None) or settings.format))
            output = format(result)

            # Perform the echo.
            secho(output, **color_info)
Example #2
0
def login(username, password, scope, client_id, client_secret, verbose):
    """
    Retrieves and stores an OAuth2 personal auth token.
    """
    if not supports_oauth():
        raise exc.TowerCLIError(
            'This version of Tower does not support OAuth2.0. Set credentials using tower-cli config.'
        )

    # Explicitly set a basic auth header for PAT acquisition (so that we don't
    # try to auth w/ an existing user+pass or oauth2 token in a config file)

    req = collections.namedtuple('req', 'headers')({})
    if client_id and client_secret:
        HTTPBasicAuth(client_id, client_secret)(req)
        req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
        r = client.post(
            '/o/token/',
            data={
                "grant_type": "password",
                "username": username,
                "password": password,
                "scope": scope
            },
            headers=req.headers
        )
    elif client_id:
        req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
        r = client.post(
            '/o/token/',
            data={
                "grant_type": "password",
                "username": username,
                "password": password,
                "client_id": client_id,
                "scope": scope
            },
            headers=req.headers
        )
    else:
        HTTPBasicAuth(username, password)(req)
        r = client.post(
            '/users/{}/personal_tokens/'.format(username),
            data={"description": "Tower CLI", "application": None, "scope": scope},
            headers=req.headers
        )

    if r.ok:
        result = r.json()
        result.pop('summary_fields', None)
        result.pop('related', None)
        if client_id:
            token = result.pop('access_token', None)
        else:
            token = result.pop('token', None)
        if settings.verbose:
            # only print the actual token if -v
            result['token'] = token
        secho(json.dumps(result, indent=1), fg='blue', bold=True)
        config.main(['oauth_token', token, '--scope=user'])
Example #3
0
 def test_color_false(self):
     """Establish that when the color setting is false, that color
     data is stripped.
     """
     with settings.runtime_values(color=False):
         with mock.patch.object(click, 'secho') as click_secho:
             secho('foo bar baz', fg='green')
             click_secho.assert_called_once_with('foo bar baz')
Example #4
0
def echo_setting(key):
    """Echo a setting to the CLI."""
    value = getattr(settings, key)
    secho('%s: ' % key, fg='magenta', bold=True, nl=False)
    secho(six.text_type(value),
        bold=True,
        fg='white' if isinstance(value, six.text_type) else 'cyan',
    )
Example #5
0
def echo_setting(key):
    """Echo a setting to the CLI."""
    value = getattr(settings, key)
    secho('%s: ' % key, fg='magenta', bold=True, nl=False)
    secho(six.text_type(value),
        bold=True,
        fg='white' if isinstance(value, six.text_type) else 'cyan',
    )
Example #6
0
 def test_color_false(self):
     """Establish that when the color setting is false, that color
     data is stripped.
     """
     with settings.runtime_values(color=False):
         with mock.patch.object(click, 'secho') as click_secho:
             secho('foo bar baz', fg='green')
             click_secho.assert_called_once_with('foo bar baz')
Example #7
0
def login(username, password, scope, client_id, client_secret, verbose):
    """
    Retrieves and stores an OAuth2 personal auth token.
    """
    if not supports_oauth():
        raise exc.TowerCLIError(
            'This version of Tower does not support OAuth2.0. Set credentials using tower-cli config.'
        )

    # Explicitly set a basic auth header for PAT acquisition (so that we don't
    # try to auth w/ an existing user+pass or oauth2 token in a config file)

    req = collections.namedtuple('req', 'headers')({})
    if client_id and client_secret:
        HTTPBasicAuth(client_id, client_secret)(req)
        req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
        r = client.post('/o/token/',
                        data={
                            "grant_type": "password",
                            "username": username,
                            "password": password,
                            "scope": scope
                        },
                        headers=req.headers)
    elif client_id:
        req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
        r = client.post('/o/token/',
                        data={
                            "grant_type": "password",
                            "username": username,
                            "password": password,
                            "client_id": client_id,
                            "scope": scope
                        },
                        headers=req.headers)
    else:
        HTTPBasicAuth(username, password)(req)
        r = client.post('/users/{}/personal_tokens/'.format(username),
                        data={
                            "description": "Tower CLI",
                            "application": None,
                            "scope": scope
                        },
                        headers=req.headers)

    if r.ok:
        result = r.json()
        result.pop('summary_fields', None)
        result.pop('related', None)
        if client_id:
            token = result.pop('access_token', None)
        else:
            token = result.pop('token', None)
        if settings.verbose:
            # only print the actual token if -v
            result['token'] = token
        secho(json.dumps(result, indent=1), fg='blue', bold=True)
        config.main(['oauth_token', token, '--scope=user'])
    def _make_request(self, method, url, args, kwargs):
        # Decide whether to require SSL verification
        verify_ssl = True
        if (settings.verify_ssl is False) or hasattr(settings, 'insecure'):
            verify_ssl = False
        elif settings.certificate is not None:
            verify_ssl = settings.certificate

        # Call the superclass method.
        try:
            with warnings.catch_warnings():
                warnings.simplefilter(
                    "ignore", urllib3.exceptions.InsecureRequestWarning)
                return super(Client, self).request(method,
                                                   url,
                                                   *args,
                                                   verify=verify_ssl,
                                                   **kwargs)
        except SSLError as ex:
            # Throw error if verify_ssl not set to false and server
            #  is not using verified certificate.
            if settings.verbose:
                debug.log('SSL connection failed:', fg='yellow', bold=True)
                debug.log(str(ex), fg='yellow', bold=True, nl=2)
            if not settings.host.startswith('http'):
                secho(
                    'Suggestion: add the correct http:// or '
                    'https:// prefix to the host configuration.',
                    fg='blue',
                    bold=True)
            raise exc.ConnectionError(
                'Could not establish a secure connection. '
                'Please add the server to your certificate '
                'authority.\nYou can run this command without verifying SSL '
                'with the --insecure flag, or permanently disable '
                'verification by the config setting:\n\n '
                'tower-cli config verify_ssl false')
        except ConnectionError as ex:
            # Throw error if server can not be reached.
            if settings.verbose:
                debug.log('Cannot connect to Tower:', fg='yellow', bold=True)
                debug.log(str(ex), fg='yellow', bold=True, nl=2)
            raise exc.ConnectionError(
                'There was a network error of some kind trying to connect '
                'to Tower.\n\nThe most common  reason for this is a settings '
                'issue; is your "host" value in `tower-cli config` correct?\n'
                'Right now it is: "%s".' % settings.host)
Example #9
0
                def func(*args, **kwargs):
                    result = method(*args, **kwargs)

                    # If this was a request that could result in a modification
                    # of data, print it in Ansible coloring.
                    color_info = {}
                    if 'changed' in result:
                        if result['changed']:
                            color_info['fg'] = 'yellow'
                        else:
                            color_info['fg'] = 'green'

                    # Piece together the result into the proper format.
                    format = getattr(self, '_format_%s' % settings.format)
                    output = format(result)

                    # Perform the echo.
                    secho(output, **color_info)
Example #10
0
                def func(*args, **kwargs):
                    result = method(*args, **kwargs)

                    # If this was a request that could result in a modification
                    # of data, print it in Ansible coloring.
                    color_info = {}
                    if 'changed' in result:
                        if result['changed']:
                            color_info['fg'] = 'yellow'
                        else:
                            color_info['fg'] = 'green'

                    # Piece together the result into the proper format.
                    format = getattr(self, '_format_%s' % settings.format)
                    output = format(result)

                    # Perform the echo.
                    secho(output, **color_info)
Example #11
0
                def func(*args, **kwargs):
                    result = method(*args, **kwargs)

                    # If this was a request that could result in a modification
                    # of data, print it in Ansible coloring.
                    color_info = {}
                    if "changed" in result:
                        if result["changed"]:
                            color_info["fg"] = "yellow"
                        else:
                            color_info["fg"] = "green"

                    # Piece together the result into the proper format.
                    format = getattr(self, "_format_%s" % settings.format)
                    output = format(result)

                    # Perform the echo.
                    secho(output, **color_info)
Example #12
0
    def _make_request(self, method, url, args, kwargs):
        # Decide whether to require SSL verification
        verify_ssl = True
        if (settings.verify_ssl is False) or hasattr(settings, 'insecure'):
            verify_ssl = False
        elif settings.certificate:
            verify_ssl = settings.certificate

        # Call the superclass method.
        try:
            with warnings.catch_warnings():
                warnings.simplefilter(
                    "ignore", urllib3.exceptions.InsecureRequestWarning)
                return super(Client, self).request(
                    method, url, *args, verify=verify_ssl, **kwargs)
        except SSLError as ex:
            # Throw error if verify_ssl not set to false and server
            #  is not using verified certificate.
            if settings.verbose:
                debug.log('SSL connection failed:', fg='yellow', bold=True)
                debug.log(str(ex), fg='yellow', bold=True, nl=2)
            if not settings.host.startswith('http'):
                secho('Suggestion: add the correct http:// or '
                      'https:// prefix to the host configuration.',
                      fg='blue', bold=True)
            raise exc.ConnectionError(
                'Could not establish a secure connection. '
                'Please add the server to your certificate '
                'authority.\nYou can run this command without verifying SSL '
                'with the --insecure flag, or permanently disable '
                'verification by the config setting:\n\n '
                'tower-cli config verify_ssl false'
            )
        except ConnectionError as ex:
            # Throw error if server can not be reached.
            if settings.verbose:
                debug.log('Cannot connect to Tower:', fg='yellow', bold=True)
                debug.log(str(ex), fg='yellow', bold=True, nl=2)
            raise exc.ConnectionError(
                'There was a network error of some kind trying to connect '
                'to Tower.\n\nThe most common  reason for this is a settings '
                'issue; is your "host" value in `tower-cli config` correct?\n'
                'Right now it is: "%s".' % settings.host
            )
Example #13
0
    def get_command(self, ctx, name):
        """Given a command identified by its name, import the appropriate
        module and return the decorated command.

        Resources are automatically commands, but if both a resource and
        a command are defined, the command takes precedence.
        """
        # First, attempt to get a basic command from `tower_cli.api.misc`.
        if name in misc.__all__:
            return getattr(misc, name)

        # No command was found; try to get a resource.
        try:
            resource = tower_cli.get_resource(name)
            return ResSubcommand(resource)
        except ImportError:
            pass

        # Okay, we weren't able to find a command.
        secho('No such command: %s.' % name, fg='red', bold=True)
        sys.exit(2)
Example #14
0
    def get_command(self, ctx, name):
        """Given a command identified by its name, import the appropriate
        module and return the decorated command.

        Resources are automatically commands, but if both a resource and
        a command are defined, the command takes precedence.
        """
        # First, attempt to get a basic command from `tower_cli.api.misc`.
        if name in misc.__all__:
            return getattr(misc, name)

        # No command was found; try to get a resource.
        try:
            resource = tower_cli.get_resource(name)
            return ResSubcommand(resource)
        except ImportError:
            pass

        # Okay, we weren't able to find a command.
        secho('No such command: %s.' % name, fg='red', bold=True)
        sys.exit(2)
Example #15
0
        def func(*args, **kwargs):
            # Echo warning if this method is deprecated.
            if getattr(method, 'deprecated', False):
                debug.log('This method is deprecated in Tower 3.0.', header='warning')

            result = method(*args, **kwargs)

            # If this was a request that could result in a modification
            # of data, print it in Ansible coloring.
            color_info = {}
            if isinstance(result, dict) and 'changed' in result:
                if result['changed']:
                    color_info['fg'] = 'yellow'
                else:
                    color_info['fg'] = 'green'

            # Piece together the result into the proper format.
            format = getattr(self, '_format_%s' % (getattr(method, 'format_freezer', None) or settings.format))
            output = format(result)

            # Perform the echo.
            secho(output, **color_info)
def log(s, header='', file=sys.stderr, nl=1, **kwargs):
    """Log the given output to stderr if and only if we are in
    verbose mode.

    If we are not in verbose mode, this is a no-op.
    """
    # Sanity check: If we are not in verbose mode, this is a no-op.
    if not settings.verbose:
        return

    # Construct multi-line string to stderr if header is provided.
    if header:
        word_arr = s.split(' ')
        multi = []
        word_arr.insert(0, '%s:' % header.upper())
        i = 0
        while i < len(word_arr):
            to_add = ['***']
            count = 3
            while count <= 79:
                count += len(word_arr[i]) + 1
                if count <= 79:
                    to_add.append(word_arr[i])
                    i += 1
                    if i == len(word_arr):
                        break
            # Handle corner case of extra-long word longer than 75 characters.
            if len(to_add) == 1:
                to_add.append(word_arr[i])
                i += 1
            if i != len(word_arr):
                count -= len(word_arr[i]) + 1
            to_add.append('*' * (78 - count))
            multi.append(' '.join(to_add))
        s = '\n'.join(multi)
        lines = len(multi)
    else:
        lines = 1

    # If `nl` is an int greater than the number of rows of a message,
    # add the appropriate newlines to the output.
    if isinstance(nl, int) and nl > lines:
        s += '\n' * (nl - lines)

    # Output to stderr.
    return secho(s, file=file, **kwargs)
Example #17
0
def log(s, header='', file=sys.stderr, nl=1, **kwargs):
    """Log the given output to stderr if and only if we are in
    verbose mode.

    If we are not in verbose mode, this is a no-op.
    """
    # Sanity check: If we are not in verbose mode, this is a no-op.
    if not settings.verbose:
        return

    # Construct multi-line string to stderr if header is provided.
    if header:
        word_arr = s.split(' ')
        multi = []
        word_arr.insert(0, '%s:' % header.upper())
        i = 0
        while i < len(word_arr):
            to_add = ['***']
            count = 3
            while count <= 79:
                count += len(word_arr[i]) + 1
                if count <= 79:
                    to_add.append(word_arr[i])
                    i += 1
                    if i == len(word_arr):
                        break
            if i != len(word_arr):
                count -= len(word_arr[i]) + 1
            to_add.append('*' * (78 - count))
            multi.append(' '.join(to_add))
        s = '\n'.join(multi)
        lines = len(multi)
    else:
        lines = 1

    # If `nl` is an int greater than the number of rows of a message,
    # add the appropriate newlines to the output.
    if isinstance(nl, int) and nl > lines:
        s += '\n' * (nl - lines)

    # Output to stderr.
    return secho(s, file=file, **kwargs)
Example #18
0
def log(s, header='', file=sys.stderr, nl=1, **kwargs):
    """Log the given output to stderr if and only if we are in
    verbose mode.

    If we are not in verbose mode, this is a no-op.
    """
    # Sanity check: If we are not in verbose mode, this is a no-op.
    if not settings.verbose:
        return

    # If this is a "header" line, make it a header.
    if header:
        s = '*** %s: %s %s' % \
            (header.upper(), s, '*' * (72 - len(header) - len(s)))

    # If `nl` is an int greater than 1, add the appropriate newlines
    # to the output.
    if isinstance(nl, int) and nl > 1:
        s += '\n' * (nl - 1)

    # Output to stderr.
    return secho(s, file=file, **kwargs)
Example #19
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.',
            'environment':
            'Options from environment variables.',
            '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.')
Example #20
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)
    try:
        os.chmod(filename, stat.S_IRUSR | stat.S_IWUSR) # give rw permissions to user only
                                           # fix for issue number 48
    except Exception as e:
        warnings.warn('Unable to set permissions on {0} - {1} '.format(filename,e),
                      UserWarning)
    click.echo('Configuration updated successfully.')
Example #21
0
    def request(self, method, url, *args, **kwargs):
        """Make a request to the Ansible Tower API, and return the
        response.
        """
        # Piece together the full URL.
        url = "%s%s" % (self.prefix, url.lstrip("/"))

        # Ansible Tower expects authenticated requests; add the authentication
        # from settings if it's provided.
        kwargs.setdefault("auth", (settings.username, settings.password))

        # POST and PUT requests will send JSON by default; make this
        # the content_type by default.  This makes it such that we don't have
        # to constantly write that in our code, which gets repetitive.
        headers = kwargs.get("headers", {})
        if method.upper() in ("PATCH", "POST", "PUT"):
            headers.setdefault("Content-Type", "application/json")
            kwargs["headers"] = headers

        # If debugging is on, print the URL and data being sent.
        debug.log("%s %s" % (method, url), fg="blue", bold=True)
        if method in ("POST", "PUT", "PATCH"):
            debug.log("Data: %s" % kwargs.get("data", {}), fg="blue", bold=True)
        if method == "GET" or kwargs.get("params", None):
            debug.log("Params: %s" % kwargs.get("params", {}), fg="blue", bold=True)
        debug.log("")

        # If this is a JSON request, encode the data value.
        if headers.get("Content-Type", "") == "application/json":
            kwargs["data"] = json.dumps(kwargs.get("data", {}))

        # Decide whether to require SSL verification
        verify_ssl = True
        if (settings.verify_ssl is False) or hasattr(settings, "insecure"):
            verify_ssl = False
        elif settings.certificate is not None:
            verify_ssl = settings.certificate

        # Call the superclass method.
        try:
            with warnings.catch_warnings():
                warnings.simplefilter("ignore", urllib3.exceptions.InsecureRequestWarning)
                r = super(Client, self).request(method, url, *args, verify=verify_ssl, **kwargs)
        except SSLError as ex:
            # Throw error if verify_ssl not set to false and server
            #  is not using verified certificate.
            if settings.verbose:
                debug.log("SSL connection failed:", fg="yellow", bold=True)
                debug.log(str(ex), fg="yellow", bold=True, nl=2)
            if not settings.host.startswith("http"):
                secho(
                    "Suggestion: add the correct http:// or " "https:// prefix to the host configuration.",
                    fg="blue",
                    bold=True,
                )
            raise exc.ConnectionError(
                "Could not establish a secure connection. "
                "Please add the server to your certificate "
                "authority.\nYou can run this command without verifying SSL "
                "with the --insecure flag, or permanently disable "
                "verification by the config setting:\n\n "
                "tower-cli config verify_ssl false"
            )
        except ConnectionError as ex:
            # Throw error if server can not be reached.
            if settings.verbose:
                debug.log("Cannot connect to Tower:", fg="yellow", bold=True)
                debug.log(str(ex), fg="yellow", bold=True, nl=2)
            raise exc.ConnectionError(
                "There was a network error of some kind trying to connect "
                "to Tower.\n\nThe most common  reason for this is a settings "
                'issue; is your "host" value in `tower-cli config` correct?\n'
                'Right now it is: "%s".' % settings.host
            )

        # Sanity check: Did the server send back some kind of internal error?
        # If so, bubble this up.
        if r.status_code >= 500:
            raise exc.ServerError("The Tower server sent back a server error. " "Please try again later.")

        # Sanity check: Did we fail to authenticate properly?
        # If so, fail out now; this is always a failure.
        if r.status_code == 401:
            raise exc.AuthError("Invalid Tower authentication credentials.")

        # Sanity check: Did we get a forbidden response, which means that
        # the user isn't allowed to do this? Report that.
        if r.status_code == 403:
            raise exc.Forbidden("You don't have permission to do that.")

        # Sanity check: Did we get a 404 response?
        # Requests with primary keys will return a 404 if there is no response,
        # and we want to consistently trap these.
        if r.status_code == 404:
            raise exc.NotFound("The requested object could not be found.")

        # Sanity check: Did we get a 405 response?
        # A 405 means we used a method that isn't allowed. Usually this
        # is a bad request, but it requires special treatment because the
        # API sends it as a logic error in a few situations (e.g. trying to
        # cancel a job that isn't running).
        if r.status_code == 405:
            raise exc.MethodNotAllowed(
                "The Tower server says you can't make a request with the " "%s method to that URL (%s)." % (method, url)
            )

        # Sanity check: Did we get some other kind of error?
        # If so, write an appropriate error message.
        if r.status_code >= 400:
            raise exc.BadRequest(
                "The Tower server claims it was sent a bad request.\n\n"
                "%s %s\nParams: %s\nData: %s\n\nResponse: %s"
                % (method, url, kwargs.get("params", None), kwargs.get("data", None), r.content.decode("utf8"))
            )

        # Django REST Framework intelligently prints API keys in the
        # order that they are defined in the models and serializer.
        #
        # We want to preserve this behavior when it is possible to do so
        # with minimal effort, because while the order has no explicit meaning,
        # we make some effort to order keys in a convenient manner.
        #
        # To this end, make this response into an APIResponse subclass
        # (defined below), which has a `json` method that doesn't lose key
        # order.
        r.__class__ = APIResponse

        # Return the response object.
        return r
Example #22
0
    def monitor(self, pk, min_interval=1, max_interval=30,
                timeout=None, outfile=sys.stdout, **kwargs):
        """Monitor a running job.

        Blocks further input until the job completes (whether successfully or
        unsuccessfully) and a final status can be given.
        """
        dots = itertools.cycle([0, 1, 2, 3])
        longest_string = 0
        interval = min_interval
        start = time.time()

        # Poll the Ansible Tower instance for status, and print the status
        # to the outfile (usually standard out).
        #
        # Note that this is one of the few places where we use `secho`
        # even though we're in a function that might theoretically be imported
        # and run in Python.  This seems fine; outfile can be set to /dev/null
        # and very much the normal use for this method should be CLI
        # monitoring.
        result = self.status(pk, detail=True)
        last_poll = time.time()
        timeout_check = 0
        while result['status'] != 'successful':
            # If the job has failed, we want to raise an Exception for that
            # so we get a non-zero response.
            if result['failed']:
                if is_tty(outfile) and not settings.verbose:
                    secho('\r' + ' ' * longest_string + '\n', file=outfile)
                raise exc.JobFailure('Job failed.')

            # Sanity check: Have we officially timed out?
            # The timeout check is incremented below, so this is checking
            # to see if we were timed out as of the previous iteration.
            # If we are timed out, abort.
            if timeout and timeout_check - start > timeout:
                raise exc.Timeout('Monitoring aborted due to timeout.')

            # If the outfile is a TTY, print the current status.
            output = '\rCurrent status: %s%s' % (result['status'],
                                                 '.' * next(dots))
            if longest_string > len(output):
                output += ' ' * (longest_string - len(output))
            else:
                longest_string = len(output)
            if is_tty(outfile) and not settings.verbose:
                secho(output, nl=False, file=outfile)

            # Put the process to sleep briefly.
            time.sleep(0.2)

            # Sanity check: Have we reached our timeout?
            # If we're about to time out, then we need to ensure that we
            # do one last check.
            #
            # Note that the actual timeout will be performed at the start
            # of the **next** iteration, so there's a chance for the job's
            # completion to be noted first.
            timeout_check = time.time()
            if timeout and timeout_check - start > timeout:
                last_poll -= interval

            # If enough time has elapsed, ask the server for a new status.
            #
            # Note that this doesn't actually do a status check every single
            # time; we want the "spinner" to spin even if we're not actively
            # doing a check.
            #
            # So, what happens is that we are "counting down" (actually up)
            # to the next time that we intend to do a check, and once that
            # time hits, we do the status check as part of the normal cycle.
            if time.time() - last_poll > interval:
                result = self.status(pk, detail=True)
                last_poll = time.time()
                interval = min(interval * 1.5, max_interval)

                # If the outfile is *not* a TTY, print a status update
                # when and only when we make an actual check to job status.
                if not is_tty(outfile) or settings.verbose:
                    click.echo('Current status: %s' % result['status'],
                               file=outfile)

            # Wipe out the previous output
            if is_tty(outfile) and not settings.verbose:
                secho('\r' + ' ' * longest_string, file=outfile, nl=False)
                secho('\r', file=outfile, nl=False)

        # Return the job ID and other response data
        answer = OrderedDict((
            ('changed', True),
            ('id', pk),
        ))
        answer.update(result)
        # Make sure to return ID of resource and not update number
        # relevant for project creation and update
        answer['id'] = pk
        return answer
Example #23
0
 def my_print(self, message=None, fg='', bold=False, nl=True):
     if self.no_color:
         secho(message, fg='', bold=False, nl=nl)
     else:
         secho(message, fg=fg, bold=bold, nl=nl)
Example #24
0
    def request(self, method, url, *args, **kwargs):
        """Make a request to the Ansible Tower API, and return the
        response.
        """
        # Piece together the full URL.
        url = '%s%s' % (self.prefix, url.lstrip('/'))

        # Ansible Tower expects authenticated requests; add the authentication
        # from settings if it's provided.
        kwargs.setdefault('auth', (settings.username, settings.password))

        # POST and PUT requests will send JSON by default; make this
        # the content_type by default.  This makes it such that we don't have
        # to constantly write that in our code, which gets repetitive.
        headers = kwargs.get('headers', {})
        if method.upper() in ('PATCH', 'POST', 'PUT'):
            headers.setdefault('Content-Type', 'application/json')
            kwargs['headers'] = headers

        # If debugging is on, print the URL and data being sent.
        debug.log('%s %s' % (method, url), fg='blue', bold=True)
        if method in ('POST', 'PUT', 'PATCH'):
            debug.log('Data: %s' % kwargs.get('data', {}),
                      fg='blue',
                      bold=True)
        if method == 'GET' or kwargs.get('params', None):
            debug.log('Params: %s' % kwargs.get('params', {}),
                      fg='blue',
                      bold=True)
        debug.log('')

        # If this is a JSON request, encode the data value.
        if headers.get('Content-Type', '') == 'application/json':
            kwargs['data'] = json.dumps(kwargs.get('data', {}))

        # Decide whether to require SSL verification
        verify_ssl = True
        if (settings.verify_ssl is False) or hasattr(settings, 'insecure'):
            verify_ssl = False

        # Call the superclass method.
        try:
            with warnings.catch_warnings():
                warnings.simplefilter(
                    "ignore", urllib3.exceptions.InsecureRequestWarning)
                r = super(Client, self).request(method,
                                                url,
                                                *args,
                                                verify=verify_ssl,
                                                **kwargs)
        except SSLError as ex:
            # Throw error if verify_ssl not set to false and server
            #  is not using verified certificate.
            if settings.verbose:
                debug.log('SSL connection failed:', fg='yellow', bold=True)
                debug.log(str(ex), fg='yellow', bold=True, nl=2)
            if not settings.host.startswith('http'):
                secho(
                    'Suggestion: add the correct http:// or '
                    'https:// prefix to the host configuration.',
                    fg='blue',
                    bold=True)
            raise exc.ConnectionError(
                'Could not establish a secure connection. '
                'Please add the server to your certificate '
                'authority.\nYou can run this command without verifying SSL '
                'with the --insecure flag, or permanently disable '
                'verification by the config setting:\n\n '
                'tower-cli config verify_ssl false')
        except ConnectionError as ex:
            # Throw error if server can not be reached.
            if settings.verbose:
                debug.log('Cannot connect to Tower:', fg='yellow', bold=True)
                debug.log(str(ex), fg='yellow', bold=True, nl=2)
            raise exc.ConnectionError(
                'There was a network error of some kind trying to connect '
                'to Tower.\n\nThe most common  reason for this is a settings '
                'issue; is your "host" value in `tower-cli config` correct?\n'
                'Right now it is: "%s".' % settings.host)

        # Sanity check: Did the server send back some kind of internal error?
        # If so, bubble this up.
        if r.status_code >= 500:
            raise exc.ServerError('The Tower server sent back a server error. '
                                  'Please try again later.')

        # Sanity check: Did we fail to authenticate properly?
        # If so, fail out now; this is always a failure.
        if r.status_code == 401:
            raise exc.AuthError('Invalid Tower authentication credentials.')

        # Sanity check: Did we get a forbidden response, which means that
        # the user isn't allowed to do this? Report that.
        if r.status_code == 403:
            raise exc.Forbidden("You don't have permission to do that.")

        # Sanity check: Did we get a 404 response?
        # Requests with primary keys will return a 404 if there is no response,
        # and we want to consistently trap these.
        if r.status_code == 404:
            raise exc.NotFound('The requested object could not be found.')

        # Sanity check: Did we get a 405 response?
        # A 405 means we used a method that isn't allowed. Usually this
        # is a bad request, but it requires special treatment because the
        # API sends it as a logic error in a few situations (e.g. trying to
        # cancel a job that isn't running).
        if r.status_code == 405:
            raise exc.MethodNotAllowed(
                "The Tower server says you can't make a request with the "
                "%s method to that URL (%s)." % (method, url), )

        # Sanity check: Did we get some other kind of error?
        # If so, write an appropriate error message.
        if r.status_code >= 400:
            raise exc.BadRequest(
                'The Tower server claims it was sent a bad request.\n\n'
                '%s %s\nParams: %s\nData: %s\n\nResponse: %s' %
                (method, url, kwargs.get('params', None),
                 kwargs.get('data', None), r.content.decode('utf8')))

        # Django REST Framework intelligently prints API keys in the
        # order that they are defined in the models and serializer.
        #
        # We want to preserve this behavior when it is possible to do so
        # with minimal effort, because while the order has no explicit meaning,
        # we make some effort to order keys in a convenient manner.
        #
        # To this end, make this response into an APIResponse subclass
        # (defined below), which has a `json` method that doesn't lose key
        # order.
        r.__class__ = APIResponse

        # Return the response object.
        return r
Example #25
0
 def my_print(self, message=None, fg='', bold=False, nl=True):
     if self.no_color:
         secho(message, fg='', bold=False, nl=nl)
     else:
         secho(message, fg=fg, bold=bold, nl=nl)
Example #26
0
    def monitor(self,
                pk,
                min_interval=1,
                max_interval=30,
                timeout=None,
                outfile=sys.stdout,
                **kwargs):
        """Monitor a running job.

        Blocks further input until the job completes (whether successfully or
        unsuccessfully) and a final status can be given.
        """
        dots = itertools.cycle([0, 1, 2, 3])
        longest_string = 0
        interval = min_interval
        start = time.time()

        # Poll the Ansible Tower instance for status, and print the status
        # to the outfile (usually standard out).
        #
        # Note that this is one of the few places where we use `secho`
        # even though we're in a function that might theoretically be imported
        # and run in Python.  This seems fine; outfile can be set to /dev/null
        # and very much the normal use for this method should be CLI
        # monitoring.
        result = self.status(pk, detail=True)
        last_poll = time.time()
        timeout_check = 0
        while result['status'] != 'successful':
            # If the job has failed, we want to raise an Exception for that
            # so we get a non-zero response.
            if result['failed']:
                if is_tty(outfile) and not settings.verbose:
                    secho('\r' + ' ' * longest_string + '\n', file=outfile)
                raise exc.JobFailure('Job failed.')

            # Sanity check: Have we officially timed out?
            # The timeout check is incremented below, so this is checking
            # to see if we were timed out as of the previous iteration.
            # If we are timed out, abort.
            if timeout and timeout_check - start > timeout:
                raise exc.Timeout('Monitoring aborted due to timeout.')

            # If the outfile is a TTY, print the current status.
            output = '\rCurrent status: %s%s' % (result['status'],
                                                 '.' * next(dots))
            if longest_string > len(output):
                output += ' ' * (longest_string - len(output))
            else:
                longest_string = len(output)
            if is_tty(outfile) and not settings.verbose:
                secho(output, nl=False, file=outfile)

            # Put the process to sleep briefly.
            time.sleep(0.2)

            # Sanity check: Have we reached our timeout?
            # If we're about to time out, then we need to ensure that we
            # do one last check.
            #
            # Note that the actual timeout will be performed at the start
            # of the **next** iteration, so there's a chance for the job's
            # completion to be noted first.
            timeout_check = time.time()
            if timeout and timeout_check - start > timeout:
                last_poll -= interval

            # If enough time has elapsed, ask the server for a new status.
            #
            # Note that this doesn't actually do a status check every single
            # time; we want the "spinner" to spin even if we're not actively
            # doing a check.
            #
            # So, what happens is that we are "counting down" (actually up)
            # to the next time that we intend to do a check, and once that
            # time hits, we do the status check as part of the normal cycle.
            if time.time() - last_poll > interval:
                result = self.status(pk, detail=True)
                last_poll = time.time()
                interval = min(interval * 1.5, max_interval)

                # If the outfile is *not* a TTY, print a status update
                # when and only when we make an actual check to job status.
                if not is_tty(outfile) or settings.verbose:
                    click.echo('Current status: %s' % result['status'],
                               file=outfile)

            # Wipe out the previous output
            if is_tty(outfile) and not settings.verbose:
                secho('\r' + ' ' * longest_string, file=outfile, nl=False)
                secho('\r', file=outfile, nl=False)

        # Return the job ID and other response data
        answer = OrderedDict((
            ('changed', True),
            ('id', pk),
        ))
        answer.update(result)
        # Make sure to return ID of resource and not update number
        # relevant for project creation and update
        answer['id'] = pk
        return answer