Пример #1
0
    def update(self, inventory_source, monitor=False, timeout=None, **kwargs):
        """Update the given inventory source."""

        # Establish that we are able to update this inventory source
        # at all.
        debug.log('Asking whether the inventory source can be updated.',
                  header='details')
        r = client.get('%s%d/update/' % (self.endpoint, inventory_source))
        if not r.json()['can_update']:
            raise exc.BadRequest('Tower says it cannot run an update against '
                                 'this inventory source.')

        # Run the update.
        debug.log('Updating the inventory source.', header='details')
        r = client.post('%s%d/update/' % (self.endpoint, inventory_source))

        # If we were told to monitor the project update's status, do so.
        if monitor:
            result = self.monitor(inventory_source, timeout=timeout)
            inventory = client.get('/inventory_sources/%d/' %
                                   result['inventory_source'])\
                              .json()['inventory']
            result['inventory'] = int(inventory)
            return result

        # Done.
        return {'status': 'ok'}
Пример #2
0
    def status(self, pk=None, detail=False, **kwargs):
        """Print the current job status."""
        # Get the job from Ansible Tower.
        debug.log('Asking for project update status.', header='details')
        project = client.get('/projects/%d/' % pk).json()

        # Determine the appropriate project update.
        if 'current_update' in project['related']:
            debug.log('A current update exists; retrieving it.',
                      header='details')
            job = client.get(project['related']['current_update'][7:]).json()
        elif project['related'].get('last_update', None):
            debug.log('No current update exists; retrieving the most '
                      'recent update.', header='details')
            job = client.get(project['related']['last_update'][7:]).json()
        else:
            raise exc.NotFound('No project updates exist.')

        # In most cases, we probably only want to know the status of the job
        # and the amount of time elapsed. However, if we were asked for
        # verbose information, provide it.
        if detail:
            return job

        # Print just the information we need.
        return {
            'elapsed': job['elapsed'],
            'failed': job['failed'],
            'status': job['status'],
        }
Пример #3
0
 def test_method_not_allowed_error(self):
     """Establish that authentication errors raise the MethodNotAllowed
     exception.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=405)
         with self.assertRaises(exc.MethodNotAllowed):
             client.get('/ping/')
Пример #4
0
 def test_server_error(self):
     """Establish that server errors raise the ServerError
     exception as expected.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=500)
         with self.assertRaises(exc.ServerError):
             client.get('/ping/')
Пример #5
0
 def test_bad_request_error(self):
     """Establish that other errors not covered above raise the
     BadRequest exception.
     """
     with client.test_mode as t:
         t.register('/ping/', "I'm a teapot!", status_code=418)
         with self.assertRaises(exc.BadRequest):
             client.get('/ping/')
Пример #6
0
 def test_not_found_error(self):
     """Establish that authentication errors raise the NotFound
     exception.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=404)
         with self.assertRaises(exc.NotFound):
             client.get('/ping/')
Пример #7
0
 def test_forbidden_error(self):
     """Establish that forbidden errors raise the ForbiddenError
     exception.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=403)
         with self.assertRaises(exc.Forbidden):
             client.get('/ping/')
Пример #8
0
 def test_auth_error(self):
     """Establish that authentication errors raise the AuthError
     exception.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=401)
         with self.assertRaises(exc.AuthError):
             client.get('/ping/')
Пример #9
0
 def test_connection_ssl_error(self):
     """Establish that if we get a ConnectionError or an SSLError
     back from requests, that we deal with it nicely.
     """
     for ErrorType in REQUESTS_ERRORS:
         with settings.runtime_values(verbose=False, host='https://foo.co'):
             with mock.patch.object(Session, 'request') as req:
                 req.side_effect = ErrorType
                 with self.assertRaises(exc.ConnectionError):
                     client.get('/ping/')
Пример #10
0
 def test_failed_suggestion_protocol(self):
     """Establish that if connection fails and protocol not given,
     tower-cli suggests that to the user."""
     with settings.runtime_values(verbose=False, host='foo.co'):
         with mock.patch.object(Session, 'request') as req:
             req.side_effect = requests.exceptions.SSLError
             with mock.patch.object(click, 'secho') as secho:
                 with self.assertRaises(exc.ConnectionError):
                     client.get('/ping/')
                 self.assertTrue(secho.called)
Пример #11
0
 def test_disable_connection_warning(self):
     """Establish that the --insecure flag will cause the program to
     call disable_warnings in the urllib3 package.
     """
     with mock.patch('requests.packages.urllib3.disable_warnings') as g:
         with client.test_mode as t:
             t.register('/ping/', "I'm a teapot!", status_code=200)
             with settings.runtime_values(insecure=False):
                 client.get('/ping/')
                 assert g.called
Пример #12
0
    def update(self, inventory_source, monitor=False, wait=False,
               timeout=None, **kwargs):
        """Update the given inventory source.

        =====API DOCS=====
        Update the given inventory source.

        :param inventory_source: Primary key or name of the inventory source to be updated.
        :type inventory_source: str
        :param monitor: Flag that if set, immediately calls ``monitor`` on the newly launched inventory update
                        rather than exiting with a success.
        :type monitor: bool
        :param wait: Flag that if set, monitor the status of the inventory update, but do not print while it is
                     in progress.
        :type wait: bool
        :param timeout: If provided with ``monitor`` flag set, this attempt will time out after the given number
                        of seconds.
        :type timeout: int
        :param `**kwargs`: Fields used to override underlyingl inventory source fields when creating and launching
                           an inventory update.
        :returns: Result of subsequent ``monitor`` call if ``monitor`` flag is on; Result of subsequent ``wait``
                  call if ``wait`` flag is on; dictionary of "status" if none of the two flags are on.
        :rtype: dict
        :raises tower_cli.exceptions.BadRequest: When the inventory source cannot be updated.

        =====API DOCS=====
        """

        # Establish that we are able to update this inventory source
        # at all.
        debug.log('Asking whether the inventory source can be updated.', header='details')
        r = client.get('%s%d/update/' % (self.endpoint, inventory_source))
        if not r.json()['can_update']:
            raise exc.BadRequest('Tower says it cannot run an update against this inventory source.')

        # Run the update.
        debug.log('Updating the inventory source.', header='details')
        r = client.post('%s%d/update/' % (self.endpoint, inventory_source), data={})
        inventory_update_id = r.json()['inventory_update']

        # If we were told to monitor the project update's status, do so.
        if monitor or wait:
            if monitor:
                result = self.monitor(inventory_update_id, parent_pk=inventory_source, timeout=timeout)
            elif wait:
                result = self.wait(inventory_update_id, parent_pk=inventory_source, timeout=timeout)
            inventory = client.get('/inventory_sources/%d/' % result['inventory_source']).json()['inventory']
            result['inventory'] = int(inventory)
            return result

        # Done.
        return {
            'id': inventory_update_id,
            'status': 'ok'
        }
Пример #13
0
 def test_connection_ssl(self):
     with client.test_mode as t:
         t.register_json('/ping/', {'status': 'ok'})
         https_adapter = client.adapters['https://']
         with mock.patch.object(FauxAdapter, 'send', wraps=https_adapter.send) as mock_send:
             client.get('/ping/')
             mock_send.assert_called_once_with(
                 mock.ANY, cert=None, proxies=mock.ANY, stream=mock.ANY,
                 timeout=mock.ANY, verify=True
             )
             self.assertTrue(mock_send.call_args[1]['verify'])
Пример #14
0
 def test_connection_ssl_error_verbose(self):
     """Establish that if we get a ConnectionError or an SSLError
     back from requests, that we deal with it nicely, and
     additionally print the internal error if verbose is True.
     """
     for ErrorType in REQUESTS_ERRORS:
         with settings.runtime_values(verbose=True, host='https://foo.co'):
             with mock.patch.object(Session, 'request') as req:
                 req.side_effect = ErrorType
                 with mock.patch.object(debug, 'log') as dlog:
                     with self.assertRaises(exc.ConnectionError):
                         client.get('/ping/')
                     self.assertEqual(dlog.call_count, 5)
Пример #15
0
    def list(self, **kwargs):
        """Return a list of objects.

        =====API DOCS=====
        Retrieve a list of Tower settings.

        :param category: The category slug in which to look up indevidual settings.
        :type category: str
        :param `**kwargs`: Keyword arguments list of available fields used for searching resource objects.
        :returns: A JSON object containing details of all resource objects returned by Tower backend.
        :rtype: dict

        =====API DOCS=====
        """
        self.custom_category = kwargs.get('category', 'all')
        try:
            result = super(Resource, self).list(**kwargs)
        except exc.NotFound as e:
            categories = map(
                lambda category: category['slug'],
                client.get('/settings/').json()['results']
            )
            e.message = '%s is not a valid category.  Choose from [%s]' % (
                kwargs['category'],
                ', '.join(categories)
            )
            raise e
        finally:
            self.custom_category = None
        return {
            'results': [{'id': k, 'value': v} for k, v in result.items()]
        }
Пример #16
0
    def list(self, root=False, **kwargs):
        """Return a list of groups."""

        # Option to list children of a parent group
        if kwargs.get('parent', None):
            self.set_child_endpoint(
                parent=kwargs['parent'],
                inventory=kwargs.get('inventory', None)
            )
            kwargs.pop('parent')

        # 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)
Пример #17
0
    def update(self, pk=None, create_on_missing=False, monitor=False,
               timeout=None, name=None, organization=None):
        """Trigger a project update job within Ansible Tower.
        Only meaningful on non-manual projects.
        """
        # First, get the appropriate project.
        # This should be uniquely identified at this point, and if not, then
        # we just want the error that `get` will throw to bubble up.
        project = self.get(pk, name=name, organization=organization)
        pk = project['id']

        # Determine whether this project is able to be updated.
        debug.log('Asking whether the project can be updated.',
                  header='details')
        result = client.get('/projects/%d/update/' % pk)
        if not result.json()['can_update']:
            raise exc.CannotStartJob('Cannot update project.')

        # Okay, this project can be updated, according to Tower.
        # Commence the update.
        debug.log('Updating the project.', header='details')
        result = client.post('/projects/%d/update/' % pk)

        # If we were told to monitor the project update's status, do so.
        if monitor:
            return self.monitor(pk, timeout=timeout)

        # Return the project update ID.
        return {
            'changed': True,
        }
Пример #18
0
    def status(self, pk=None, detail=False, **kwargs):
        """Print the current job status. This is used to check a running job.
        You can look up the job with the same parameters used for a get
        request."""
        # Remove default values (anything where the value is None).
        self._pop_none(kwargs)

        # Search for the record if pk not given
        if not pk:
            job = self.get(include_debug_header=True, **kwargs)
        # Get the job from Ansible Tower if pk given
        else:
            debug.log('Asking for job status.', header='details')
            finished_endpoint = '%s%d/' % (self.endpoint, pk)
            job = client.get(finished_endpoint).json()

        # In most cases, we probably only want to know the status of the job
        # and the amount of time elapsed. However, if we were asked for
        # verbose information, provide it.
        if detail:
            return job

        # Print just the information we need.
        return adict({
            'elapsed': job['elapsed'],
            'failed': job['failed'],
            'status': job['status'],
        })
Пример #19
0
def version():
    """Display full version information."""

    # Print out the current version of Tower CLI.
    click.echo('Tower CLI %s' % __version__)

    # Print out the current API version of the current code base.
    click.echo('API %s' % CUR_API_VERSION)

    # Attempt to connect to the Ansible Tower server.
    # If we succeed, print a version; if not, generate a failure.
    try:
        r = client.get('/config/')
    except RequestException as ex:
        raise exc.TowerCLIError('Could not connect to Ansible Tower.\n%s' %
                                six.text_type(ex))
    config = r.json()
    license = config.get('license_info', {}).get('license_type', 'open')
    if license == 'open':
        server_type = 'AWX'
    else:
        server_type = 'Ansible Tower'
    click.echo('%s %s' % (server_type, config['version']))

    # Print out Ansible version of server
    click.echo('Ansible %s' % config['ansible_version'])
Пример #20
0
    def callback(self, pk=None, host_config_key='', extra_vars=None):
        """Contact Tower and request a configuration update using this job template.

        =====API DOCS=====
        Contact Tower and request a provisioning callback using this job template.

        :param pk: Primary key of the job template to run provisioning callback against.
        :type pk: int
        :param host_config_key: Key string used to authenticate the callback host.
        :type host_config_key: str
        :param extra_vars: Extra variables that are passed to provisioning callback.
        :type extra_vars: array of str
        :returns: A dictionary of a single key "changed", which indicates whether the provisioning callback
                  is successful.
        :rtype: dict

        =====API DOCS=====
        """
        url = self.endpoint + '%s/callback/' % pk
        if not host_config_key:
            host_config_key = client.get(url).json()['host_config_key']
        post_data = {'host_config_key': host_config_key}
        if extra_vars:
            post_data['extra_vars'] = parser.process_extra_vars(list(extra_vars), force_json=True)
        r = client.post(url, data=post_data, auth=None)
        if r.status_code == 201:
            return {'changed': True}
Пример #21
0
    def insights(self, pk=None, **kwargs):
        """Return a JSON object of host insights.

        Note global option --format is not available here, as the output would always be JSON-formatted.
        """
        res = self.get(pk=pk, **kwargs)
        url = self.endpoint + '%d/%s/' % (res['id'], 'insights')
        return client.get(url, params={}).json()
Пример #22
0
    def last_job_data(self, pk=None, **kwargs):
        """
        Internal utility function for Unified Job Templates
        Returns data about the last job run off of that UJT
        """
        ujt = self.get(pk, include_debug_header=True, **kwargs)

        # Determine the appropriate inventory source update.
        if 'current_update' in ujt['related']:
            debug.log('A current job; retrieving it.', header='details')
            return client.get(ujt['related']['current_update'][7:]).json()
        elif ujt['related'].get('last_update', None):
            debug.log('No current job or update exists; retrieving the most '
                      'recent.', header='details')
            return client.get(ujt['related']['last_update'][7:]).json()
        else:
            raise exc.NotFound('No related jobs or updates exist.')
Пример #23
0
 def test_server_error(self):
     """Establish that server errors raise the ServerError
     exception as expected.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=500)
         with self.assertRaises(exc.ServerError):
             r = client.get('/ping/')
Пример #24
0
 def survey(self, pk=None, **kwargs):
     """Get the survey_spec for the job template.
     To write a survey, use the modify command with the --survey-spec
     parameter."""
     job_template = self.get(pk=pk, **kwargs)
     if settings.format == 'human':
         settings.format = 'json'
     return client.get(self._survey_endpoint(job_template['id'])).json()
Пример #25
0
 def test_forbidden_error(self):
     """Establish that forbidden errors raise the ForbiddenError
     exception.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=403)
         with self.assertRaises(exc.Forbidden):
             r = client.get('/ping/')
Пример #26
0
 def test_bad_request_error(self):
     """Establish that other errors not covered above raise the
     BadRequest exception.
     """
     with client.test_mode as t:
         t.register('/ping/', "I'm a teapot!", status_code=418)
         with self.assertRaises(exc.BadRequest):
             r = client.get('/ping/')
Пример #27
0
 def test_insecure_connection(self):
     """Establish that the --insecure flag will cause the program to
     call request with verify=False.
     """
     with mock.patch('requests.sessions.Session.request') as g:
         mock_response = type('statobj', (), {})()  # placeholder object
         mock_response.status_code = 200
         g.return_value = mock_response
         with client.test_mode as t:
             t.register('/ping/', "I'm a teapot!", status_code=200)
             with settings.runtime_values(verify_ssl=False):
                 client.get('/ping/')
                 g.assert_called_once_with(
                     # The point is to assure verify=False below
                     'GET', mock.ANY, allow_redirects=True,
                     auth=mock.ANY, verify=False
                 )
Пример #28
0
def tower_check_mode(module):
    '''Execute check mode logic for Ansible Tower modules'''
    if module.check_mode:
        try:
            result = client.get('/ping').json()
            module.exit_json(changed=True, tower_version='{0}'.format(result['version']))
        except (exc.ServerError, exc.ConnectionError, exc.BadRequest) as excinfo:
            module.fail_json(changed=False, msg='Failed check mode: {0}'.format(excinfo))
Пример #29
0
 def test_auth_error(self):
     """Establish that authentication errors raise the AuthError
     exception.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=401)
         with self.assertRaises(exc.AuthError):
             r = client.get('/ping/')
Пример #30
0
 def test_insecure_connection(self):
     """Establish that the --insecure flag will cause the program to
     call request with verify=False.
     """
     with mock.patch('requests.sessions.Session.request') as g:
         mock_response = type('statobj', (), {})()  # placeholder object
         mock_response.status_code = 200
         g.return_value = mock_response
         with client.test_mode as t:
             t.register('/ping/', "I'm a teapot!", status_code=200)
             with settings.runtime_values(verify_ssl=False):
                 client.get('/ping/')
                 g.assert_called_once_with(
                     # The point is to assure verify=False below
                     'GET', mock.ANY, allow_redirects=True,
                     auth=mock.ANY, verify=False
                 )
Пример #31
0
 def test_method_not_allowed_error(self):
     """Establish that authentication errors raise the MethodNotAllowed
     exception.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=405)
         with self.assertRaises(exc.MethodNotAllowed):
             r = client.get('/ping/')
Пример #32
0
 def test_not_found_error(self):
     """Establish that authentication errors raise the NotFound
     exception.
     """
     with client.test_mode as t:
         t.register('/ping/', 'ERRORED!!', status_code=404)
         with self.assertRaises(exc.NotFound):
             r = client.get('/ping/')
def tower_check_mode(module):
    '''Execute check mode logic for Ansible Tower modules'''
    if module.check_mode:
        try:
            result = client.get('/ping').json()
            module.exit_json(changed=True, tower_version='{0}'.format(result['version']))
        except (exc.ServerError, exc.ConnectionError, exc.BadRequest) as excinfo:
            module.fail_json(changed=False, msg='Failed check mode: {0}'.format(excinfo))
Пример #34
0
 def test_connection_error(self):
     """Establish that if we get a ConnectionError back from requests,
     that we deal with it nicely.
     """
     with settings.runtime_values(verbose=False):
         with mock.patch.object(Session, 'request') as req:
             req.side_effect = requests.exceptions.ConnectionError
             with self.assertRaises(exc.ConnectionError):
                 r = client.get('/ping/')
Пример #35
0
    def launch(self, monitor=False, wait=False, timeout=None, **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.

        =====API DOCS=====
        Launch a new ad-hoc command.

        :param monitor: Flag that if set, immediately calls ``monitor`` on the newly launched command rather
                        than exiting with a success.
        :type monitor: bool
        :param wait: Flag that if set, monitor the status of the job, but do not print while job is in progress.
        :type wait: bool
        :param timeout: If provided with ``monitor`` flag set, this attempt will time out after the given number
                        of seconds.
        :type timeout: int
        :param `**kwargs`: Fields needed to create and launch an ad hoc command.
        :returns: Result of subsequent ``monitor`` call if ``monitor`` flag is on; Result of subsequent ``wait``
                  call if ``wait`` flag is on; dictionary of "id" and "changed" if none of the two flags are on.
        :rtype: dict
        :raises tower_cli.exceptions.TowerCLIError: When ad hoc commands are not available in Tower backend.

        =====API DOCS=====
        """
        # 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)

        # 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
Пример #36
0
 def callback(self, pk=None, host_config_key='', extra_vars=None):
     """Contact Tower and request a configuration update using this job template."""
     url = self.endpoint + '%s/callback/' % pk
     if not host_config_key:
         host_config_key = client.get(url).json()['host_config_key']
     post_data = {'host_config_key': host_config_key}
     if extra_vars:
         post_data['extra_vars'] = parser.process_extra_vars(list(extra_vars), force_json=True)
     r = client.post(url, data=post_data, auth=None)
     if r.status_code == 201:
         return {'changed': True}
Пример #37
0
 def test_connection_error_verbose(self):
     """Establish that if we get a ConnectionError back from requests,
     that we deal with it nicely, and additionally print the internal error
     if verbose is True.
     """
     with settings.runtime_values(verbose=True):
         with mock.patch.object(Session, 'request') as req:
             req.side_effect = requests.exceptions.ConnectionError
             with mock.patch.object(debug, 'log') as dlog:
                 with self.assertRaises(exc.ConnectionError):
                     r = client.get('/ping/')
                 self.assertEqual(dlog.call_count, 5)
Пример #38
0
    def update(self,
               inventory_source,
               monitor=False,
               wait=False,
               timeout=None,
               **kwargs):
        """Update the given inventory source."""

        # Establish that we are able to update this inventory source
        # at all.
        debug.log('Asking whether the inventory source can be updated.',
                  header='details')
        r = client.get('%s%d/update/' % (self.endpoint, inventory_source))
        if not r.json()['can_update']:
            raise exc.BadRequest('Tower says it cannot run an update against '
                                 'this inventory source.')

        # Run the update.
        debug.log('Updating the inventory source.', header='details')
        r = client.post('%s%d/update/' % (self.endpoint, inventory_source))

        # If we were told to monitor the project update's status, do so.
        if monitor or wait:
            inventory_update_id = r.json()['inventory_update']
            if monitor:
                result = self.monitor(inventory_update_id,
                                      parent_pk=inventory_source,
                                      timeout=timeout)
            elif wait:
                result = self.wait(inventory_update_id,
                                   parent_pk=inventory_source,
                                   timeout=timeout)
            inventory = client.get('/inventory_sources/%d/' %
                                   result['inventory_source'])\
                              .json()['inventory']
            result['inventory'] = int(inventory)
            return result

        # Done.
        return {'status': 'ok'}
Пример #39
0
    def sync(self, inventory_source, **kwargs):
        """Update the given inventory source."""

        # Establish that we are able to update this inventory source
        # at all.
        r = client.get('%s%d/update/' % (self.endpoint, inventory_source))
        if not r.json()['can_update']:
            raise exc.BadRequest('Tower says it cannot run an update against '
                                 'this inventory source.')

        # Run the update.
        r = client.post('%s%d/update/' % (self.endpoint, inventory_source))
        return {'status': 'ok'}
Пример #40
0
 def list(self, **kwargs):
     """Return a list of objects."""
     self.custom_category = kwargs.get('category', 'all')
     try:
         result = super(Resource, self).list(**kwargs)
     except exc.NotFound as e:
         categories = map(lambda category: category['slug'],
                          client.get('/settings/').json()['results'])
         e.message = '%s is not a valid category.  Choose from [%s]' % (
             kwargs['category'], ', '.join(categories))
         raise e
     finally:
         self.custom_category = None
     return {'results': [{'id': k, 'value': v} for k, v in result.items()]}
Пример #41
0
def version(**kwargs):
    """Display version information."""

    # Print out the current version of Tower CLI.
    click.echo('Tower CLI %s' % __version__)

    # Attempt to connect to the Ansible Tower server.
    # If we succeed, print a version; if not, generate a failure.
    try:
        r = client.get('/config/')
        click.echo('Ansible Tower %s' % r.json()['version'])
    except RequestException as ex:
        raise TowerCLIError('Could not connect to Ansible Tower.\n%s' %
                            six.text_type(ex))
Пример #42
0
def version():
    """Display version information."""

    # Print out the current version of Tower CLI.
    click.echo('Tower CLI %s' % __version__)

    # Attempt to connect to the Ansible Tower server.
    # If we succeed, print a version; if not, generate a failure.
    try:
        r = client.get('/config/')
        click.echo('Ansible Tower %s' % r.json()['version'])
    except RequestException as ex:
        raise TowerCLIError('Could not connect to Ansible Tower.\n%s' %
                            six.text_type(ex))
Пример #43
0
    def _assoc(self, url_fragment, me, other):
        """Associate the `other` record with the `me` record."""

        # Get the endpoint for foreign records within this object.
        url = self.endpoint + '%d/%s/' % (me, url_fragment)

        # Attempt to determine whether the other record already exists here,
        # for the "changed" moniker.
        r = client.get(url, params={'id': other}).json()
        if r['count'] > 0:
            return {'changed': False}

        # Send a request adding the other record to this one.
        r = client.post(url, data={'associate': True, 'id': other})
        return {'changed': True}
Пример #44
0
    def _assoc(self, url_fragment, me, other):
        """Associate the `other` record with the `me` record."""

        # Get the endpoint for foreign records within this object.
        url = self.endpoint + '%d/%s/' % (me, url_fragment)

        # Attempt to determine whether the other record already exists here,
        # for the "changed" moniker.
        r = client.get(url, params={'id': other}).json()
        if r['count'] > 0:
            return {'changed': False}

        # Send a request adding the other record to this one.
        r = client.post(url, data={'associate': True, 'id': other})
        return {'changed': True}
Пример #45
0
    def _disassoc(self, url_fragment, me, other):
        """Disassociate the `other` record from the `me` record."""

        # Get the endpoint for foreign records within this object.
        url = self.endpoint + '%d/%s/' % (me, url_fragment)

        # Attempt to determine whether the other record already is absent, for
        # the "changed" moniker.
        r = client.get(url, params={'id': other}).json()
        if r['count'] == 0:
            return {'changed': False}

        # Send a request removing the foreign record from this one.
        r = client.post(url, data={'disassociate': True, 'id': other})
        return {'changed': True}
Пример #46
0
    def _disassoc(self, url_fragment, me, other):
        """Disassociate the `other` record from the `me` record."""

        # Get the endpoint for foreign records within this object.
        url = self.endpoint + '%d/%s/' % (me, url_fragment)

        # Attempt to determine whether the other record already is absent, for
        # the "changed" moniker.
        r = client.get(url, params={'id': other}).json()
        if r['count'] == 0:
            return {'changed': False}

        # Send a request removing the foreign record from this one.
        r = client.post(url, data={'disassociate': True, 'id': other})
        return {'changed': True}
Пример #47
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
Пример #48
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)
Пример #49
0
    def insights(self, pk=None, **kwargs):
        """Return a JSON object of host insights.

        Note global option --format is not available here, as the output would always be JSON-formatted.

        =====API DOCS=====
        List host insights.

        :param pk: Primary key of the target host.
        :type pk: int
        :param `**kwargs`: Keyword arguments list of available fields used for searching resource objects.
        :returns: A JSON object of host insights.
        :rtype: dict
        =====API DOCS=====
        """
        res = self.get(pk=pk, **kwargs)
        url = self.endpoint + '%d/%s/' % (res['id'], 'insights')
        return client.get(url, params={}).json()
Пример #50
0
    def status(self, pk, detail=False):
        """Print the current job status."""
        # Get the job from Ansible Tower.
        debug.log('Asking for job status.', header='details')
        job = client.get('/jobs/%d/' % pk).json()

        # In most cases, we probably only want to know the status of the job
        # and the amount of time elapsed. However, if we were asked for
        # verbose information, provide it.
        if detail:
            return job

        # Print just the information we need.
        return adict({
            'elapsed': job['elapsed'],
            'failed': job['failed'],
            'status': job['status'],
        })
Пример #51
0
    def status(self, pk, detail=False):
        """Print the current job status."""
        # Get the job from Ansible Tower.
        debug.log('Asking for job status.', header='details')
        job = client.get('/jobs/%d/' % pk).json()

        # In most cases, we probably only want to know the status of the job
        # and the amount of time elapsed. However, if we were asked for
        # verbose information, provide it.
        if detail:
            return job

        # Print just the information we need.
        return adict({
            'elapsed': job['elapsed'],
            'failed': job['failed'],
            'status': job['status'],
        })
Пример #52
0
    def update(self,
               pk=None,
               create_on_missing=False,
               monitor=False,
               wait=False,
               timeout=None,
               name=None,
               organization=None):
        """Trigger a project update job within Ansible Tower.
        Only meaningful on non-manual projects.
        """
        # First, get the appropriate project.
        # This should be uniquely identified at this point, and if not, then
        # we just want the error that `get` will throw to bubble up.
        project = self.get(pk, name=name, organization=organization)
        pk = project['id']

        # Determine whether this project is able to be updated.
        debug.log('Asking whether the project can be updated.',
                  header='details')
        result = client.get('/projects/%d/update/' % pk)
        if not result.json()['can_update']:
            raise exc.CannotStartJob('Cannot update project.')

        # Okay, this project can be updated, according to Tower.
        # Commence the update.
        debug.log('Updating the project.', header='details')
        result = client.post('/projects/%d/update/' % pk)

        # If we were told to monitor the project update's status, do so.
        if monitor:
            project_update_id = result.json()['project_update']
            return self.monitor(project_update_id,
                                parent_pk=pk,
                                timeout=timeout)
        elif wait:
            project_update_id = result.json()['project_update']
            return self.wait(project_update_id, parent_pk=pk, timeout=timeout)

        # Return the project update ID.
        return {
            'changed': True,
        }
Пример #53
0
    def lookup_stdout(self, pk=None, start_line=None, end_line=None,
                      full=True):
        """
        Internal utility function to return standard out
        requires the pk of a unified job
        """
        stdout_url = '%s%s/stdout/' % (self.unified_job_type, pk)
        payload = {
            'format': 'json', 'content_encoding': 'base64',
            'content_format': 'ansi'}
        if start_line:
            payload['start_line'] = start_line
        if end_line:
            payload['end_line'] = end_line
        debug.log('Requesting a copy of job standard output', header='details')
        resp = client.get(stdout_url, params=payload).json()
        content = b64decode(resp['content'])

        return content
Пример #54
0
    def test_request_ok(self):
        """Establish that a request that returns a valid JSON response
        returns without incident and comes back as an APIResponse.
        """
        with client.test_mode as t:
            t.register_json('/ping/', {'status': 'ok'})
            r = client.get('/ping/')

            # Establish that our response is an APIResponse and that our
            # JSONification method returns back an ordered dict.
            self.assertIsInstance(r, APIResponse)
            self.assertIsInstance(r.json(), OrderedDict)

            # Establish that our headers have expected auth.
            request = r.request
            self.assertEqual(request.headers['Authorization'],
                             'Basic bWVhZ2FuOlRoaXMgaXMgdGhlIGJlc3Qgd2luZS4=')

            # Make sure the content matches what we expect.
            self.assertEqual(r.json(), {'status': 'ok'})
Пример #55
0
    def list(self, root=False, **kwargs):
        """Return a list of groups.

        =====API DOCS=====
        Retrieve a list of groups.

        :param root: Flag that if set, only root groups of a specific inventory will be listed.
        :type root: bool
        :param parent: Primary key or name of the group whose child groups will be listed.
        :type parent: str
        :param all_pages: Flag that if set, collect all pages of content from the API when returning results.
        :type all_pages: bool
        :param page: The page to show. Ignored if all_pages is set.
        :type page: int
        :param query: Contains 2-tuples used as query parameters to filter resulting resource objects.
        :type query: list
        :param `**kwargs`: Keyword arguments list of available fields used for searching resource objects.
        :returns: A JSON object containing details of all resource objects returned by Tower backend.
        :rtype: dict
        :raises tower_cli.exceptions.UsageError: When ``root`` flag is on and ``inventory`` is not present in
                                                 ``**kwargs``.

        =====API DOCS=====
        """
        # Option to list children of a parent group
        if kwargs.get('parent', None):
            self.set_child_endpoint(parent=kwargs['parent'],
                                    inventory=kwargs.get('inventory', None))
            kwargs.pop('parent')
        # 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)
Пример #56
0
    def read(self,
             pk=None,
             fail_on_no_results=False,
             fail_on_multiple_results=False,
             **kwargs):
        """Retrieve and return objects from the Ansible Tower API.

        If an `object_id` is provided, only attempt to read that object,
        rather than the list at large.

        If `fail_on_no_results` is True, then zero results is considered
        a failure case and raises an exception; otherwise, empty list is
        returned. (Note: This is always True if a primary key is included.)

        If `fail_on_multiple_results` is True, then at most one result is
        expected, and more results constitutes a failure case.
        (Note: This is meaningless if a primary key is included, as there can
        never be multiple results.)
        """
        # Piece together the URL we will be hitting.
        url = self.endpoint
        if pk:
            url += '%d/' % pk

        # Pop the query parameter off of the keyword arguments; it will
        # require special handling (below).
        queries = kwargs.pop('query', [])

        # Remove default values (anything where the value is None).
        # click is unfortunately bad at the way it sends through unspecified
        # defaults.
        for key, value in copy(kwargs).items():
            if value is None:
                kwargs.pop(key)
            if hasattr(value, 'read'):
                kwargs[key] = value.read()

        # If queries were provided, process them.
        for query in queries:
            if query[0] in kwargs:
                raise exc.BadRequest('Attempted to set %s twice.' % query[0])
            kwargs[query[0]] = query[1]

        # Make the request to the Ansible Tower API.
        r = client.get(url, params=kwargs)
        resp = r.json()

        # If this was a request with a primary key included, then at the
        # point that we got a good result, we know that we're done and can
        # return the result.
        if pk:
            # Make the results all look the same, for easier parsing
            # by other methods.
            #
            # Note that the `get` method will effectively undo this operation,
            # but that's a good thing, because we might use `get` without a
            # primary key.
            return {'count': 1, 'results': [resp]}

        # Did we get zero results back when we shouldn't?
        # If so, this is an error, and we need to complain.
        if fail_on_no_results and resp['count'] == 0:
            raise exc.NotFound('The requested object could not be found.')

        # Did we get more than one result back?
        # If so, this is also an error, and we need to complain.
        if fail_on_multiple_results and resp['count'] >= 2:
            raise exc.MultipleResults('Expected one result, got %d. Tighten '
                                      'your criteria.' % resp['count'])

        # Return the response.
        return resp
def main():
    argument_spec = dict(
        workflow_template=dict(required=True),
        extra_vars=dict(required=False),
        wait=dict(required=False, default=True, type='bool'),
        timeout=dict(required=False, default=None, type='int'),
    )

    module = TowerModule(argument_spec=argument_spec, supports_check_mode=True)

    workflow_template = module.params.get('workflow_template')
    extra_vars = module.params.get('extra_vars')
    wait = module.params.get('wait')
    timeout = module.params.get('timeout')

    # If we are going to use this result to return we can consider ourselfs changed
    result = dict(changed=False, msg='initial message')

    tower_auth = tower_auth_config(module)
    with settings.runtime_values(**tower_auth):
        # First we will test the connection. This will be a test for both check and run mode
        # Note, we are not using the tower_check_mode method here because we want to do more than just a ping test
        # If we are in check mode we also want to validate that we can find the workflow
        try:
            ping_result = client.get('/ping').json()
            # Stuff the version into the results as an FYI
            result['tower_version'] = ping_result['version']
        except (ServerError, ConnectionError, BadRequest) as excinfo:
            result['msg'] = "Failed to reach Tower: {0}".format(excinfo)
            module.fail_json(**result)

        # Now that we know we can connect, lets verify that we can resolve the workflow_template
        try:
            workflow = tower_cli.get_resource("workflow").get(
                **{'name': workflow_template})
        except TowerCLIError as e:
            result['msg'] = "Failed to find workflow: {0}".format(e)
            module.fail_json(**result)

        # Since we were able to find the workflow, if we are in check mode we can return now
        if module.check_mode:
            result['msg'] = "Check mode passed"
            module.exit_json(**result)

        # We are no ready to run the workflow
        try:
            result['job_info'] = tower_cli.get_resource('workflow_job').launch(
                workflow_job_template=workflow['id'],
                monitor=False,
                wait=wait,
                timeout=timeout,
                extra_vars=extra_vars)
            if wait:
                # If we were waiting for a result we will fail if the workflow failed
                if result['job_info']['failed']:
                    result['msg'] = "Workflow execution failed"
                    module.fail_json(**result)
                else:
                    module.exit_json(**result)

            # We were not waiting and there should be no way we can make it here without the workflow fired off so we can return a success
            module.exit_json(**result)

        except TowerCLIError as e:
            result['msg'] = "Failed to execute workflow: {0}".format(e)
            module.fail_json(**result)