Esempio n. 1
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'])
Esempio n. 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'])
Esempio n. 3
0
    def role_write(self, fail_on_found=False, disassociate=False, **kwargs):
        """Re-implementation of the parent `write` method specific to roles.
        Adds a grantee (user or team) to the resource's role."""

        # Get the role, using only the resource data
        data, self.endpoint = self.data_endpoint(kwargs, ignore=['obj'])
        debug.log('Checking if role exists.', header='details')
        response = self.read(pk=None,
                             fail_on_no_results=True,
                             fail_on_multiple_results=True,
                             **data)
        role_data = response['results'][0]
        role_id = role_data['id']

        # Role exists, change display settings to output something
        self.configure_display(role_data, kwargs, write=True)

        # Check if user/team has this role
        # Implictly, force_on_exists is false for roles
        obj, obj_type, res, res_type = self.obj_res(kwargs)
        debug.log('Checking if %s already has role.' % obj_type,
                  header='details')
        data, self.endpoint = self.data_endpoint(kwargs)
        data['content_type__model'] = res_type.replace('_', '')
        response = self.read(pk=None,
                             fail_on_no_results=False,
                             fail_on_multiple_results=False,
                             **data)

        msg = ''
        if response['count'] > 0 and not disassociate:
            msg = 'This %s is already a member of the role.' % obj_type
        elif response['count'] == 0 and disassociate:
            msg = 'This %s is already a non-member of the role.' % obj_type

        if msg:
            role_data['changed'] = False
            if fail_on_found:
                raise exc.NotFound(msg)
            else:
                debug.log(msg, header='DECISION')
                return role_data

        # Add or remove the user/team to the role
        debug.log('Attempting to %s the %s in this role.' %
                  ('remove' if disassociate else 'add', obj_type),
                  header='details')
        post_data = {'id': role_id}
        if disassociate:
            post_data['disassociate'] = True
        client.post('%s/%s/roles/' % (grammar.pluralize(obj_type), obj),
                    data=post_data)
        role_data['changed'] = True
        return role_data
Esempio n. 4
0
    def role_write(self, fail_on_found=False, disassociate=False, **kwargs):
        """Re-implementation of the parent `write` method specific to roles.
        Adds a grantee (user or team) to the resource's role."""

        # Get the role, using only the resource data
        data, self.endpoint = self.data_endpoint(kwargs, ignore=['obj'])
        debug.log('Checking if role exists.', header='details')
        response = self.read(pk=None, fail_on_no_results=True,
                             fail_on_multiple_results=True, **data)
        role_data = response['results'][0]
        role_id = role_data['id']

        # Role exists, change display settings to output something
        self.configure_display(role_data, kwargs, write=True)

        # Check if user/team has this role
        # Implictly, force_on_exists is false for roles
        obj, obj_type, res, res_type = self.obj_res(kwargs)
        debug.log('Checking if %s already has role.' % obj_type,
                  header='details')
        data, self.endpoint = self.data_endpoint(kwargs)
        data['content_type__model'] = res_type.replace('_', '')
        response = self.read(pk=None, fail_on_no_results=False,
                             fail_on_multiple_results=False, **data)

        msg = ''
        if response['count'] > 0 and not disassociate:
            msg = 'This %s is already a member of the role.' % obj_type
        elif response['count'] == 0 and disassociate:
            msg = 'This %s is already a non-member of the role.' % obj_type

        if msg:
            role_data['changed'] = False
            if fail_on_found:
                raise exc.NotFound(msg)
            else:
                debug.log(msg, header='DECISION')
                return role_data

        # Add or remove the user/team to the role
        debug.log('Attempting to %s the %s in this role.' % (
            'remove' if disassociate else 'add', obj_type), header='details')
        post_data = {'id': role_id}
        if disassociate:
            post_data['disassociate'] = True
        client.post('%s/%s/roles/' % (grammar.pluralize(obj_type), obj),
                    data=post_data)
        role_data['changed'] = True
        return role_data
Esempio n. 5
0
    def relaunch(self, pk=None, **kwargs):
        """Relaunch a stopped job.

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

        relaunch_endpoint = '%s%s/relaunch/' % (self.endpoint, pk)
        data = {}
        # Attempt to relaunch the job.
        answer = {}
        try:
            result = client.post(relaunch_endpoint, data=data).json()
            if 'id' in result:
                answer.update(result)
            answer['changed'] = True
        except exc.MethodNotAllowed:
            answer['changed'] = False

        # Return the answer.
        return answer
Esempio n. 6
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,
        }
Esempio n. 7
0
    def cancel(self, pk, fail_if_not_running=False):
        """Cancel a currently running job.

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

        # Return a success.
        return adict({'status': 'canceled', 'changed': changed})
Esempio n. 8
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'}
Esempio n. 9
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,
        }
Esempio n. 10
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}
Esempio n. 11
0
    def modify(self, setting, value):
        """Modify an already existing object."""
        prev_value = new_value = self.get(setting)['value']
        answer = OrderedDict()
        encrypted = '$encrypted$' in six.text_type(prev_value)

        if encrypted or six.text_type(prev_value) != six.text_type(value):
            if setting == 'LICENSE':
                r = client.post('/config/',
                                data=self.coerce_type(setting, value))
                new_value = r.json()
            else:
                r = client.patch(
                    self.endpoint,
                    data={setting: self.coerce_type(setting, value)})
                new_value = r.json()[setting]
            answer.update(r.json())

        changed = encrypted or (prev_value != new_value)

        answer.update({
            'changed': changed,
            'id': setting,
            'value': new_value,
        })
        return answer
Esempio n. 12
0
    def modify(self, setting, value):
        """Modify an already existing object.

        Positional argument SETTING is the setting name and VALUE is its value,
        which can be provided directly or obtained from a file name if prefixed with '@'.
        """
        prev_value = new_value = self.get(setting)['value']
        answer = OrderedDict()
        encrypted = '$encrypted$' in six.text_type(prev_value)

        if encrypted or six.text_type(prev_value) != six.text_type(value):
            if setting == 'LICENSE':
                r = client.post('/config/',
                                data=self.coerce_type(setting, value))
                new_value = r.json()
            else:
                r = client.patch(
                    self.endpoint,
                    data={setting: self.coerce_type(setting, value)})
                new_value = r.json()[setting]
            answer.update(r.json())

        changed = encrypted or (prev_value != new_value)

        answer.update({
            'changed': changed,
            'id': setting,
            'value': new_value,
        })
        return answer
Esempio n. 13
0
    def cancel(self, pk, fail_if_not_running=False):
        """Cancel a currently running job.

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

        # Return a success.
        return adict({'status': 'canceled', 'changed': changed})
Esempio n. 14
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'}
Esempio n. 15
0
    def launch(self,
               workflow_job_template=None,
               monitor=False,
               wait=False,
               timeout=None,
               extra_vars=None,
               **kwargs):
        """Launch a new workflow job based on a workflow job template.

        Creates a new workflow job in Ansible Tower, starts it, and
        returns back an ID in order for its status to be monitored.
        """
        if len(extra_vars) > 0:
            kwargs['extra_vars'] = parser.process_extra_vars(extra_vars)

        debug.log('Launching the workflow job.', header='details')
        self._pop_none(kwargs)
        post_response = client.post(
            'workflow_job_templates/{}/launch/'.format(workflow_job_template),
            data=kwargs).json()

        workflow_job_id = post_response['id']
        post_response['changed'] = True

        if monitor:
            return self.monitor(workflow_job_id, timeout=timeout)
        elif wait:
            return self.wait(workflow_job_id, timeout=timeout)

        return post_response
Esempio n. 16
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}
Esempio n. 17
0
    def batch_update(self, pk=None, **kwargs):
        """Update all related inventory sources of the given inventory.

        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'], 'update_inventory_sources')
        return client.post(url, data={}).json()
Esempio n. 18
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
Esempio n. 19
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'
        }
Esempio n. 20
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}
Esempio n. 21
0
    def launch(self,
               workflow_job_template=None,
               monitor=False,
               wait=False,
               timeout=None,
               extra_vars=None,
               **kwargs):
        """Launch a new workflow job based on a workflow job template.

        Creates a new workflow job in Ansible Tower, starts it, and
        returns back an ID in order for its status to be monitored.

        =====API DOCS=====
        Launch a new workflow job based on a workflow job template.

        :param workflow_job_template: Primary key or name of the workflow job template to launch new job.
        :type workflow_job_template: str
        :param monitor: Flag that if set, immediately calls ``monitor`` on the newly launched workflow job rather
                        than exiting with a success.
        :type monitor: bool
        :param wait: Flag that if set, monitor the status of the workflow 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 extra_vars: yaml formatted texts that contains extra variables to pass on.
        :type extra_vars: array of strings
        :param `**kwargs`: Fields needed to create and launch a workflow job.
        :returns: Result of subsequent ``monitor`` call if ``monitor`` flag is on; Result of subsequent ``wait``
                  call if ``wait`` flag is on; loaded JSON output of the job launch if none of the two flags are on.
        :rtype: dict

        =====API DOCS=====
        """
        if extra_vars is not None and len(extra_vars) > 0:
            kwargs['extra_vars'] = parser.process_extra_vars(extra_vars)

        debug.log('Launching the workflow job.', header='details')
        self._pop_none(kwargs)
        post_response = client.post(
            'workflow_job_templates/{0}/launch/'.format(workflow_job_template),
            data=kwargs).json()

        workflow_job_id = post_response['id']
        post_response['changed'] = True

        if monitor:
            return self.monitor(workflow_job_id, timeout=timeout)
        elif wait:
            return self.wait(workflow_job_id, timeout=timeout)

        return post_response
Esempio n. 22
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'}
Esempio n. 23
0
    def test_request_post(self):
        """Establish that on a POST request, we encode the provided data
        to JSON automatically.
        """
        with client.test_mode as t:
            t.register_json('/ping/', {'status': 'ok'}, method='POST')
            r = client.post('/ping/', {'payload': 'this is my payload.'})

            # Establish that our request has the expected payload, and
            # is sent using an application/json content type.
            headers = r.request.headers
            self.assertEqual(headers['Content-Type'], 'application/json')
            self.assertEqual(r.request.body,
                             '{"payload": "this is my payload."}')
Esempio n. 24
0
    def cancel(self, pk=None, fail_if_not_running=False, **kwargs):
        """Cancel a currently running job.

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

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

        # Return a success.
        return adict({'status': 'canceled', 'changed': changed})
Esempio n. 25
0
    def test_request_post(self):
        """Establish that on a POST request, we encode the provided data
        to JSON automatically.
        """
        with client.test_mode as t:
            t.register_json('/ping/', {'status': 'ok'}, method='POST')
            r = client.post('/ping/', {'payload': 'this is my payload.'})

            # Establish that our request has the expected payload, and
            # is sent using an application/json content type.
            headers = r.request.headers
            self.assertEqual(headers['Content-Type'], 'application/json')
            self.assertEqual(r.request.body,
                             '{"payload": "this is my payload."}')
Esempio n. 26
0
    def cancel(self, pk=None, fail_if_not_running=False, **kwargs):
        """Cancel a currently running job.

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

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

        # Return a success.
        return {'status': 'canceled', 'changed': changed}
 def _get_or_create_child(self, parent, relationship, **kwargs):
     ujt_pk = kwargs.get('unified_job_template', None)
     if ujt_pk is None:
         raise exceptions.BadRequest(
             'A child node must be specified by one of the options '
             'unified-job-template, job-template, project, or '
             'inventory-source')
     kwargs.update(self._parent_filter(parent, relationship, **kwargs))
     response = self.read(
         fail_on_no_results=False, fail_on_multiple_results=False, **kwargs)
     if len(response['results']) == 0:
         debug.log('Creating new workflow node.', header='details')
         return client.post(self.endpoint, data=kwargs).json()
     else:
         return response['results'][0]
Esempio n. 28
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}
Esempio n. 29
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}
Esempio n. 30
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}
Esempio n. 31
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}
Esempio n. 32
0
    def launch(self, workflow_job_template=None, monitor=False, wait=False,
               timeout=None, extra_vars=None, **kwargs):
        """Launch a new workflow job based on a workflow job template.

        Creates a new workflow job in Ansible Tower, starts it, and
        returns back an ID in order for its status to be monitored.

        =====API DOCS=====
        Launch a new workflow job based on a workflow job template.

        :param workflow_job_template: Primary key or name of the workflow job template to launch new job.
        :type workflow_job_template: str
        :param monitor: Flag that if set, immediately calls ``monitor`` on the newly launched workflow job rather
                        than exiting with a success.
        :type monitor: bool
        :param wait: Flag that if set, monitor the status of the workflow 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 extra_vars: yaml formatted texts that contains extra variables to pass on.
        :type extra_vars: array of strings
        :param `**kwargs`: Fields needed to create and launch a workflow job.
        :returns: Result of subsequent ``monitor`` call if ``monitor`` flag is on; Result of subsequent ``wait``
                  call if ``wait`` flag is on; loaded JSON output of the job launch if none of the two flags are on.
        :rtype: dict

        =====API DOCS=====
        """
        if extra_vars is not None and len(extra_vars) > 0:
            kwargs['extra_vars'] = parser.process_extra_vars(extra_vars)

        debug.log('Launching the workflow job.', header='details')
        self._pop_none(kwargs)
        post_response = client.post('workflow_job_templates/{0}/launch/'.format(
            workflow_job_template), data=kwargs).json()

        workflow_job_id = post_response['id']
        post_response['changed'] = True

        if monitor:
            return self.monitor(workflow_job_id, timeout=timeout)
        elif wait:
            return self.wait(workflow_job_id, timeout=timeout)

        return post_response
Esempio n. 33
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
Esempio n. 34
0
    def modify(self, setting, value):
        """Modify an already existing object.

        Positional argument SETTING is the setting name and VALUE is its value,
        which can be provided directly or obtained from a file name if prefixed with '@'.

        =====API DOCS=====
        Modify an already existing Tower setting.

        :param setting: The name of the Tower setting to be modified.
        :type setting: str
        :param value: The new value of the Tower setting.
        :type value: str
        :returns: A dictionary combining the JSON output of the modified resource, as well as two extra fields:
                  "changed", a flag indicating if the resource is successfully updated; "id", an integer which
                  is the primary key of the updated object.
        :rtype: dict

        =====API DOCS=====
        """
        prev_value = new_value = self.get(setting)['value']
        answer = OrderedDict()
        encrypted = '$encrypted$' in six.text_type(prev_value)

        if encrypted or six.text_type(prev_value) != six.text_type(value):
            if setting == 'LICENSE':
                r = client.post('/config/',
                                data=self.coerce_type(setting, value))
                new_value = r.json()
            else:
                r = client.patch(
                    self.endpoint,
                    data={setting: self.coerce_type(setting, value)}
                )
                new_value = r.json()[setting]
            answer.update(r.json())

        changed = encrypted or (prev_value != new_value)

        answer.update({
            'changed': changed,
            'id': setting,
            'value': new_value,
        })
        return answer
    def modify(self, setting, value):
        """Modify an already existing object.

        Positional argument SETTING is the setting name and VALUE is its value,
        which can be provided directly or obtained from a file name if prefixed with '@'.

        =====API DOCS=====
        Modify an already existing Tower setting.

        :param setting: The name of the Tower setting to be modified.
        :type setting: str
        :param value: The new value of the Tower setting.
        :type value: str
        :returns: A dictionary combining the JSON output of the modified resource, as well as two extra fields:
                  "changed", a flag indicating if the resource is successfully updated; "id", an integer which
                  is the primary key of the updated object.
        :rtype: dict

        =====API DOCS=====
        """
        prev_value = new_value = self.get(setting)['value']
        answer = OrderedDict()
        encrypted = '$encrypted$' in six.text_type(prev_value)

        if encrypted or six.text_type(prev_value) != six.text_type(value):
            if setting == 'LICENSE':
                r = client.post('/config/',
                                data=self.coerce_type(setting, value))
                new_value = r.json()
            else:
                r = client.patch(
                    self.endpoint,
                    data={setting: self.coerce_type(setting, value)}
                )
                new_value = r.json()[setting]
            answer.update(r.json())

        changed = encrypted or (prev_value != new_value)

        answer.update({
            'changed': changed,
            'id': setting,
            'value': new_value,
        })
        return answer
    def batch_update(self, pk=None, **kwargs):
        """Update all related inventory sources of the given inventory.

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

        =====API DOCS=====
        Update all related inventory sources of the given inventory.

        :param pk: Primary key of the given inventory.
        :type pk: int
        :param `**kwargs`: Keyword arguments list of available fields used for searching resource objects.
        :returns: A JSON object of update status of the given inventory.
        :rtype: dict
        =====API DOCS=====
        """
        res = self.get(pk=pk, **kwargs)
        url = self.endpoint + '%d/%s/' % (res['id'], 'update_inventory_sources')
        return client.post(url, data={}).json()
Esempio n. 37
0
    def launch(self, monitor=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)

        # Return the command ID and other response data
        answer = OrderedDict((
            ('changed', True),
            ('id', command_id),
        ))
        answer.update(result.json())
        return answer
Esempio n. 38
0
 def write(self, pk=None, **kwargs):
     survey_input = kwargs.pop('survey_spec', None)
     if kwargs.get('extra_vars', None):
         kwargs['extra_vars'] = parser.process_extra_vars(
             kwargs['extra_vars'])
     ret = super(SurveyResource, self).write(pk=pk, **kwargs)
     if survey_input is not None and ret.get('id', None):
         if not isinstance(survey_input, dict):
             survey_input = json.loads(survey_input.strip(' '))
         if survey_input == {}:
             debug.log('Deleting the survey_spec.', header='details')
             r = client.delete(self._survey_endpoint(ret['id']))
         else:
             debug.log('Saving the survey_spec.', header='details')
             r = client.post(self._survey_endpoint(ret['id']),
                             data=survey_input)
         if r.status_code == 200:
             ret['changed'] = True
         if survey_input and not ret['survey_enabled']:
             debug.log('For survey to take effect, set survey_enabled'
                       ' field to True.', header='warning')
     return ret
Esempio n. 39
0
    def launch(self,
               job_template=None,
               monitor=False,
               timeout=None,
               no_input=True,
               extra_vars=None,
               **kwargs):
        """Launch a new job based on a job template.

        Creates a new job in Ansible Tower, immediately starts it, and
        returns back an ID in order for its status to be monitored.
        """
        # Get the job template from Ansible Tower.
        # This is used as the baseline for starting the job.

        tags = kwargs.get('tags', None)
        use_job_endpoint = kwargs.pop('use_job_endpoint', False)
        jt_resource = get_resource('job_template')
        jt = jt_resource.get(job_template)

        # Update the job data by adding an automatically-generated job name,
        # and removing the ID.
        data = copy(jt)
        data['job_template'] = data.pop('id')
        data['name'] = '%s [invoked via. Tower CLI]' % data['name']
        if tags:
            data['job_tags'] = tags

        # Initialize an extra_vars list that starts with the job template
        # preferences first, if they exist
        extra_vars_list = []
        if 'extra_vars' in data and len(data['extra_vars']) > 0:
            # But only do this for versions before 2.3
            debug.log('Getting version of Tower.', header='details')
            r = client.get('/config/')
            if LooseVersion(r.json()['version']) < LooseVersion('2.4'):
                extra_vars_list = [data['extra_vars']]

        # Add the runtime extra_vars to this list
        if extra_vars:
            extra_vars_list += list(extra_vars)  # accept tuples

        # If the job template requires prompting for extra variables,
        # do so (unless --no-input is set).
        if data.pop('ask_variables_on_launch', False) and not no_input \
                and not extra_vars:
            # If JT extra_vars are JSON, echo them to user as YAML
            initial = parser.process_extra_vars([data['extra_vars']],
                                                force_json=False)
            initial = '\n'.join((
                '# Specify extra variables (if any) here as YAML.',
                '# Lines beginning with "#" denote comments.',
                initial,
            ))
            extra_vars = click.edit(initial) or ''
            if extra_vars != initial:
                extra_vars_list = [extra_vars]

        # Data is starting out with JT variables, and we only want to
        # include extra_vars that come from the algorithm here.
        data.pop('extra_vars', None)

        # Replace/populate data fields if prompted.
        modified = set()
        for resource in PROMPT_LIST:
            if data.pop('ask_' + resource + '_on_launch', False) \
               and not no_input or use_job_endpoint:
                resource_object = kwargs.get(resource, None)
                if type(resource_object) == types.Related:
                    resource_class = get_resource(resource)
                    resource_object = resource_class.get(resource).\
                        pop('id', None)
                if resource_object is None:
                    if not use_job_endpoint:
                        debug.log(
                            '{0} is asked at launch but not provided'.format(
                                resource),
                            header='warning')
                elif resource != 'tags':
                    data[resource] = resource_object
                    modified.add(resource)

        # Dump extra_vars into JSON string for launching job
        if len(extra_vars_list) > 0:
            data['extra_vars'] = parser.process_extra_vars(extra_vars_list,
                                                           force_json=True)

        # In Tower 2.1 and later, we create the new job with
        # /job_templates/N/launch/; in Tower 2.0 and before, there is a two
        # step process of posting to /jobs/ and then /jobs/N/start/.
        supports_job_template_launch = False
        if 'launch' in jt['related']:
            supports_job_template_launch = True

        # Create the new job in Ansible Tower.
        start_data = {}
        if supports_job_template_launch and not use_job_endpoint:
            endpoint = '/job_templates/%d/launch/' % jt['id']
            if 'extra_vars' in data and len(data['extra_vars']) > 0:
                start_data['extra_vars'] = data['extra_vars']
            if tags:
                start_data['job_tags'] = data['job_tags']
            for resource in PROMPT_LIST:
                if resource in modified:
                    start_data[resource] = data[resource]
        else:
            debug.log('Creating the job.', header='details')
            job = client.post('/jobs/', data=data).json()
            job_id = job['id']
            endpoint = '/jobs/%d/start/' % job_id

        # There's a non-trivial chance that we are going to need some
        # additional information to start the job; in particular, many jobs
        # rely on passwords entered at run-time.
        #
        # If there are any such passwords on this job, ask for them now.
        debug.log('Asking for information necessary to start the job.',
                  header='details')
        job_start_info = client.get(endpoint).json()
        for password in job_start_info.get('passwords_needed_to_start', []):
            start_data[password] = getpass('Password for %s: ' % password)

        # Actually start the job.
        debug.log('Launching the job.', header='details')
        self._pop_none(kwargs)
        kwargs.update(start_data)
        job_started = client.post(endpoint, data=kwargs)

        # If this used the /job_template/N/launch/ route, get the job
        # ID from the result.
        if supports_job_template_launch and not use_job_endpoint:
            job_id = job_started.json()['job']

        # Get some information about the running job to print
        result = self.status(pk=job_id, detail=True)
        result['changed'] = True

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

        return result
Esempio n. 40
0
    def launch(
        self, job_template=None, tags=None, monitor=False, timeout=None, no_input=True, extra_vars=None, **kwargs
    ):
        """Launch a new job based on a job template.

        Creates a new job in Ansible Tower, immediately starts it, and
        returns back an ID in order for its status to be monitored.
        """
        # Get the job template from Ansible Tower.
        # This is used as the baseline for starting the job.
        jt_resource = get_resource("job_template")
        jt = jt_resource.get(job_template)

        # Update the job data by adding an automatically-generated job name,
        # and removing the ID.
        data = copy(jt)
        data["job_template"] = data.pop("id")
        data["name"] = "%s [invoked via. Tower CLI]" % data["name"]
        if tags:
            data["job_tags"] = tags

        # Initialize an extra_vars list that starts with the job template
        # preferences first, if they exist
        extra_vars_list = []
        if "extra_vars" in data and len(data["extra_vars"]) > 0:
            # But only do this for versions before 2.3
            debug.log("Getting version of Tower.", header="details")
            r = client.get("/config/")
            if LooseVersion(r.json()["version"]) < LooseVersion("2.4"):
                extra_vars_list = [data["extra_vars"]]

        # Add the runtime extra_vars to this list
        if extra_vars:
            extra_vars_list += list(extra_vars)  # accept tuples

        # If the job template requires prompting for extra variables,
        # do so (unless --no-input is set).
        if data.pop("ask_variables_on_launch", False) and not no_input and not extra_vars:
            # If JT extra_vars are JSON, echo them to user as YAML
            initial = parser.process_extra_vars([data["extra_vars"]], force_json=False)
            initial = "\n".join(
                (
                    "# Specify extra variables (if any) here as YAML.",
                    '# Lines beginning with "#" denote comments.',
                    initial,
                )
            )
            extra_vars = click.edit(initial) or ""
            if extra_vars != initial:
                extra_vars_list = [extra_vars]

        # Data is starting out with JT variables, and we only want to
        # include extra_vars that come from the algorithm here.
        data.pop("extra_vars", None)

        # Dump extra_vars into JSON string for launching job
        if len(extra_vars_list) > 0:
            data["extra_vars"] = parser.process_extra_vars(extra_vars_list, force_json=True)

        # In Tower 2.1 and later, we create the new job with
        # /job_templates/N/launch/; in Tower 2.0 and before, there is a two
        # step process of posting to /jobs/ and then /jobs/N/start/.
        supports_job_template_launch = False
        if "launch" in jt["related"]:
            supports_job_template_launch = True

        # Create the new job in Ansible Tower.
        start_data = {}
        if supports_job_template_launch:
            endpoint = "/job_templates/%d/launch/" % jt["id"]
            if "extra_vars" in data and len(data["extra_vars"]) > 0:
                start_data["extra_vars"] = data["extra_vars"]
            if tags:
                start_data["job_tags"] = data["job_tags"]
        else:
            debug.log("Creating the job.", header="details")
            job = client.post("/jobs/", data=data).json()
            job_id = job["id"]
            endpoint = "/jobs/%d/start/" % job_id

        # There's a non-trivial chance that we are going to need some
        # additional information to start the job; in particular, many jobs
        # rely on passwords entered at run-time.
        #
        # If there are any such passwords on this job, ask for them now.
        debug.log("Asking for information necessary to start the job.", header="details")
        job_start_info = client.get(endpoint).json()
        for password in job_start_info.get("passwords_needed_to_start", []):
            start_data[password] = getpass("Password for %s: " % password)

        # Actually start the job.
        debug.log("Launching the job.", header="details")
        self._pop_none(kwargs)
        kwargs.update(start_data)
        job_started = client.post(endpoint, data=kwargs)

        # If this used the /job_template/N/launch/ route, get the job
        # ID from the result.
        if supports_job_template_launch:
            job_id = job_started.json()["job"]

        # Get some information about the running job to print
        result = self.status(pk=job_id, detail=True)
        result["changed"] = True

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

        return result
Esempio n. 41
0
    def launch(self, job_template=None, tags=None, monitor=False, timeout=None,
               no_input=True, extra_vars=None, **kwargs):
        """Launch a new job based on a job template.

        Creates a new job in Ansible Tower, immediately starts it, and
        returns back an ID in order for its status to be monitored.
        """
        # Get the job template from Ansible Tower.
        # This is used as the baseline for starting the job.
        jt_resource = get_resource('job_template')
        jt = jt_resource.get(job_template)

        # Update the job data by adding an automatically-generated job name,
        # and removing the ID.
        data = copy(jt)
        data['job_template'] = data.pop('id')
        data['name'] = '%s [invoked via. Tower CLI]' % data['name']
        if tags:
            data['job_tags'] = tags

        # Initialize an extra_vars list that starts with the job template
        # preferences first, if they exist
        extra_vars_list = []
        if 'extra_vars' in data and len(data['extra_vars']) > 0:
            # But only do this for versions before 2.3
            r = client.get('/config/')
            if LooseVersion(r.json()['version']) < LooseVersion('2.4'):
                extra_vars_list = [data['extra_vars']]

        # Add the runtime extra_vars to this list
        if extra_vars:
            extra_vars_list += extra_vars

        # Call parser utility to process extra_vars, if any are present
        if len(extra_vars_list) > 0:
            data['extra_vars'] = parser. \
                extra_vars_loader_wrapper(extra_vars_list)

        # If the job template requires prompting for extra variables,
        # do so (unless --no-input is set).
        if data.pop('ask_variables_on_launch', False) and not no_input \
                and not extra_vars:
            initial = data['extra_vars']
            initial = '\n'.join((
                '# Specify extra variables (if any) here.',
                '# Lines beginning with "#" are ignored.',
                initial,
            ))
            extra_vars = click.edit(initial) or ''
            extra_vars = '\n'.join([i for i in extra_vars.split('\n')
                                    if not i.startswith('#')])
            data['extra_vars'] = extra_vars

        # In Tower 2.1 and later, we create the new job with
        # /job_templates/N/launch/; in Tower 2.0 and before, there is a two
        # step process of posting to /jobs/ and then /jobs/N/start/.
        supports_job_template_launch = False
        if 'launch' in jt['related']:
            supports_job_template_launch = True

        # Create the new job in Ansible Tower.
        start_data = {}
        if supports_job_template_launch:
            endpoint = '/job_templates/%d/launch/' % jt['id']
            if 'extra_vars' in data and len(data['extra_vars']) > 0:
                start_data['extra_vars'] = data['extra_vars']
            if tags:
                start_data['job_tags'] = data['job_tags']
        else:
            debug.log('Creating the job.', header='details')
            job = client.post('/jobs/', data=data).json()
            job_id = job['id']
            endpoint = '/jobs/%d/start/' % job_id

        # There's a non-trivial chance that we are going to need some
        # additional information to start the job; in particular, many jobs
        # rely on passwords entered at run-time.
        #
        # If there are any such passwords on this job, ask for them now.
        debug.log('Asking for information necessary to start the job.',
                  header='details')
        job_start_info = client.get(endpoint).json()
        for password in job_start_info.get('passwords_needed_to_start', []):
            start_data[password] = getpass('Password for %s: ' % password)

        # Actually start the job.
        debug.log('Launching the job.', header='details')
        self._pop_none(kwargs)
        kwargs.update(start_data)
        job_started = client.post(endpoint, data=kwargs)

        # If this used the /job_template/N/launch/ route, get the job
        # ID from the result.
        if supports_job_template_launch:
            job_id = job_started.json()['job']

        # Get some information about the running job to print
        result = self.status(pk=job_id, detail=True)
        result['changed'] = True

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

        return result
Esempio n. 42
0
    def launch(self, job_template=None, monitor=False, timeout=None,
               no_input=True, extra_vars=None, **kwargs):
        """Launch a new job based on a job template.

        Creates a new job in Ansible Tower, immediately starts it, and
        returns back an ID in order for its status to be monitored.
        """
        # Get the job template from Ansible Tower.
        # This is used as the baseline for starting the job.

        tags = kwargs.get('tags', None)
        use_job_endpoint = kwargs.pop('use_job_endpoint', False)
        jt_resource = get_resource('job_template')
        jt = jt_resource.get(job_template)

        # Update the job data by adding an automatically-generated job name,
        # and removing the ID.
        data = copy(jt)
        data['job_template'] = data.pop('id')
        data['name'] = '%s [invoked via. Tower CLI]' % data['name']
        if tags:
            data['job_tags'] = tags

        # Initialize an extra_vars list that starts with the job template
        # preferences first, if they exist
        extra_vars_list = []
        if 'extra_vars' in data and len(data['extra_vars']) > 0:
            # But only do this for versions before 2.3
            debug.log('Getting version of Tower.', header='details')
            r = client.get('/config/')
            if LooseVersion(r.json()['version']) < LooseVersion('2.4'):
                extra_vars_list = [data['extra_vars']]

        # Add the runtime extra_vars to this list
        if extra_vars:
            extra_vars_list += list(extra_vars)  # accept tuples

        # If the job template requires prompting for extra variables,
        # do so (unless --no-input is set).
        if data.pop('ask_variables_on_launch', False) and not no_input \
                and not extra_vars:
            # If JT extra_vars are JSON, echo them to user as YAML
            initial = parser.process_extra_vars(
                [data['extra_vars']], force_json=False
            )
            initial = '\n'.join((
                '# Specify extra variables (if any) here as YAML.',
                '# Lines beginning with "#" denote comments.',
                initial,
            ))
            extra_vars = click.edit(initial) or ''
            if extra_vars != initial:
                extra_vars_list = [extra_vars]

        # Data is starting out with JT variables, and we only want to
        # include extra_vars that come from the algorithm here.
        data.pop('extra_vars', None)

        # Replace/populate data fields if prompted.
        modified = set()
        for resource in PROMPT_LIST:
            if data.pop('ask_' + resource + '_on_launch', False) \
               and not no_input or use_job_endpoint:
                resource_object = kwargs.get(resource, None)
                if type(resource_object) == types.Related:
                    resource_class = get_resource(resource)
                    resource_object = resource_class.get(resource).\
                        pop('id', None)
                if resource_object is None:
                    if not use_job_endpoint:
                        debug.log('{0} is asked at launch but not provided'.
                                  format(resource), header='warning')
                elif resource != 'tags':
                    data[resource] = resource_object
                    modified.add(resource)

        # Dump extra_vars into JSON string for launching job
        if len(extra_vars_list) > 0:
            data['extra_vars'] = parser.process_extra_vars(
                extra_vars_list, force_json=True
            )

        # In Tower 2.1 and later, we create the new job with
        # /job_templates/N/launch/; in Tower 2.0 and before, there is a two
        # step process of posting to /jobs/ and then /jobs/N/start/.
        supports_job_template_launch = False
        if 'launch' in jt['related']:
            supports_job_template_launch = True

        # Create the new job in Ansible Tower.
        start_data = {}
        if supports_job_template_launch and not use_job_endpoint:
            endpoint = '/job_templates/%d/launch/' % jt['id']
            if 'extra_vars' in data and len(data['extra_vars']) > 0:
                start_data['extra_vars'] = data['extra_vars']
            if tags:
                start_data['job_tags'] = data['job_tags']
            for resource in PROMPT_LIST:
                if resource in modified:
                    start_data[resource] = data[resource]
        else:
            debug.log('Creating the job.', header='details')
            job = client.post('/jobs/', data=data).json()
            job_id = job['id']
            endpoint = '/jobs/%d/start/' % job_id

        # There's a non-trivial chance that we are going to need some
        # additional information to start the job; in particular, many jobs
        # rely on passwords entered at run-time.
        #
        # If there are any such passwords on this job, ask for them now.
        debug.log('Asking for information necessary to start the job.',
                  header='details')
        job_start_info = client.get(endpoint).json()
        for password in job_start_info.get('passwords_needed_to_start', []):
            start_data[password] = getpass('Password for %s: ' % password)

        # Actually start the job.
        debug.log('Launching the job.', header='details')
        self._pop_none(kwargs)
        kwargs.update(start_data)
        job_started = client.post(endpoint, data=kwargs)

        # If this used the /job_template/N/launch/ route, get the job
        # ID from the result.
        if supports_job_template_launch and not use_job_endpoint:
            job_id = job_started.json()['job']

        # If returning json indicates any ignored fields, display it in
        # verbose mode.
        ignored_fields = job_started.json().get('ignored_fields', {})
        has_ignored_fields = False
        for key, value in ignored_fields.items():
            if value and value != '{}':
                if not has_ignored_fields:
                    debug.log('List of ignored fields on the server side:',
                              header='detail')
                    has_ignored_fields = True
                debug.log('{0}: {1}'.format(key, value))

        # Get some information about the running job to print
        result = self.status(pk=job_id, detail=True)
        result['changed'] = True

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

        return result
Esempio n. 43
0
    def launch(self, job_template, monitor=False, timeout=None,
                     no_input=True, extra_vars=None):
        """Launch a new job based on a job template.

        Creates a new job in Ansible Tower, immediately stats it, and
        returns back an ID in order for its status to be monitored.
        """
        # Get the job template from Ansible Tower.
        # This is used as the baseline for starting the job.
        jt_resource = get_resource('job_template')
        jt = jt_resource.get(job_template)

        # Update the job data by adding an automatically-generated job name,
        # and removing the ID.
        data = copy(jt)
        data.pop('id')
        data['name'] = '%s [invoked via. Tower CLI]' % data['name']

        # If the job template requires prompting for extra variables,
        # do so (unless --no-input is set).
        if extra_vars:
            data['extra_vars'] = extra_vars.read()
        elif data.pop('ask_variables_on_launch', False) and not no_input:
            initial = data['extra_vars']
            initial = '\n'.join((
                '# Specify extra variables (if any) here.',
                '# Lines beginning with "#" are ignored.',
                initial,
            ))
            extra_vars = click.edit(initial) or ''
            extra_vars = '\n'.join([i for i in extra_vars.split('\n')
                                            if not i.startswith('#')])
            data['extra_vars'] = extra_vars

        # Create the new job in Ansible Tower.
        debug.log('Creating the job.', header='details')
        job = client.post('/jobs/', data=data).json()

        # There's a non-trivial chance that we are going to need some
        # additional information to start the job; in particular, many jobs
        # rely on passwords entered at run-time.
        #
        # If there are any such passwords on this job, ask for them now.
        debug.log('Asking for information necessary to start the job.',
                  header='details')
        job_start_info = client.get('/jobs/%d/start/' % job['id']).json()
        start_data = {}
        for password in job_start_info.get('passwords_needed_to_start', []):
            start_data[password] = getpass('Password for %s: ' % password)

        # Actually start the job.
        debug.log('Launching the job.', header='details')
        result = client.post('/jobs/%d/start/' % job['id'], start_data)

        # If we were told to monitor the job once it started, then call
        # monitor from here.
        if monitor:
            return self.monitor(job['id'], timeout=timeout)

        # Return the job ID.
        return {
            'changed': True,
            'id': job['id'],
        }
Esempio n. 44
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.

        =====API DOCS=====
        Update the given project.

        :param pk: Primary key of the project to be updated.
        :type pk: int
        :param monitor: Flag that if set, immediately calls ``monitor`` on the newly launched project update
                        rather than exiting with a success.
        :type monitor: bool
        :param wait: Flag that if set, monitor the status of the project 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 name: Name of the project to be updated if ``pk`` is not set.
        :type name: str
        :param organization: Primary key or name of the organization the project to be updated belonging to if
                             ``pk`` is not set.
        :type organization: str
        :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.CannotStartJob: When the project cannot be updated.

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

        project_update_id = result.json()['project_update']

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

        # Return the project update ID.
        return {
            'id': project_update_id,
            'changed': True,
        }
    def launch(self,
               job_template=None,
               monitor=False,
               wait=False,
               timeout=None,
               no_input=True,
               extra_vars=None,
               **kwargs):
        """Launch a new job based on a job template.

        Creates a new job in Ansible Tower, immediately starts it, and
        returns back an ID in order for its status to be monitored.

        =====API DOCS=====
        Launch a new job based on a job template.

        :param job_template: Primary key or name of the job template to launch new job.
        :type job_template: str
        :param monitor: Flag that if set, immediately calls ``monitor`` on the newly launched job 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 no_input: Flag that if set, suppress any requests for input.
        :type no_input: bool
        :param extra_vars: yaml formatted texts that contains extra variables to pass on.
        :type extra_vars: array of strings
        :param diff_mode: Specify diff mode for job template to run.
        :type diff_mode: bool
        :param limit: Specify host limit for job template to run.
        :type limit: str
        :param tags: Specify tagged actions in the playbook to run.
        :type tags: str
        :param skip_tags: Specify tagged actions in the playbook to omit.
        :type skip_tags: str
        :param job_type: Specify job type for job template to run.
        :type job_type: str
        :param verbosity: Specify verbosity of the playbook run.
        :type verbosity: int
        :param inventory: Specify machine credential for job template to run.
        :type inventory: str
        :param credential: Specify machine credential for job template to run.
        :type credential: str
        :returns: Result of subsequent ``monitor`` call if ``monitor`` flag is on; Result of subsequent
                  ``wait`` call if ``wait`` flag is on; Result of subsequent ``status`` call if none of
                  the two flags are on.
        :rtype: dict

        =====API DOCS=====
        """
        # Get the job template from Ansible Tower.
        # This is used as the baseline for starting the job.

        tags = kwargs.get('tags', None)
        jt_resource = get_resource('job_template')
        jt = jt_resource.get(job_template)

        # Update the job data by adding an automatically-generated job name,
        # and removing the ID.
        data = {}
        if tags:
            data['job_tags'] = tags

        # Initialize an extra_vars list that starts with the job template
        # preferences first, if they exist
        extra_vars_list = []
        if 'extra_vars' in data and len(data['extra_vars']) > 0:
            # But only do this for versions before 2.3
            debug.log('Getting version of Tower.', header='details')
            r = client.get('/config/')
            if LooseVersion(r.json()['version']) < LooseVersion('2.4'):
                extra_vars_list = [data['extra_vars']]

        # Add the runtime extra_vars to this list
        if extra_vars:
            extra_vars_list += list(extra_vars)  # accept tuples

        # If the job template requires prompting for extra variables,
        # do so (unless --no-input is set).
        if jt.get('ask_variables_on_launch', False) and not no_input \
                and not extra_vars:
            # If JT extra_vars are JSON, echo them to user as YAML
            initial = parser.process_extra_vars([jt['extra_vars']],
                                                force_json=False)
            initial = '\n'.join((
                '# Specify extra variables (if any) here as YAML.',
                '# Lines beginning with "#" denote comments.',
                initial,
            ))
            extra_vars = click.edit(initial) or ''
            if extra_vars != initial:
                extra_vars_list = [extra_vars]

        # Data is starting out with JT variables, and we only want to
        # include extra_vars that come from the algorithm here.
        data.pop('extra_vars', None)

        # Replace/populate data fields if prompted.
        modified = set()
        for resource in PROMPT_LIST:
            if jt.pop('ask_' + resource + '_on_launch',
                      False) and not no_input:
                resource_object = kwargs.get(resource, None)
                if type(resource_object) == types.Related:
                    resource_class = get_resource(resource)
                    resource_object = resource_class.get(resource).pop(
                        'id', None)
                if resource_object is None:
                    debug.log('{0} is asked at launch but not provided'.format(
                        resource),
                              header='warning')
                elif resource != 'tags':
                    data[resource] = resource_object
                    modified.add(resource)

        # Dump extra_vars into JSON string for launching job
        if len(extra_vars_list) > 0:
            data['extra_vars'] = parser.process_extra_vars(extra_vars_list,
                                                           force_json=True)

        # Create the new job in Ansible Tower.
        start_data = {}
        endpoint = '/job_templates/%d/launch/' % jt['id']
        if 'extra_vars' in data and len(data['extra_vars']) > 0:
            start_data['extra_vars'] = data['extra_vars']
        if tags:
            start_data['job_tags'] = data['job_tags']
        for resource in PROMPT_LIST:
            if resource in modified:
                start_data[resource] = data[resource]

        # There's a non-trivial chance that we are going to need some
        # additional information to start the job; in particular, many jobs
        # rely on passwords entered at run-time.
        #
        # If there are any such passwords on this job, ask for them now.
        debug.log('Asking for information necessary to start the job.',
                  header='details')
        job_start_info = client.get(endpoint).json()
        for password in job_start_info.get('passwords_needed_to_start', []):
            start_data[password] = getpass('Password for %s: ' % password)

        # Actually start the job.
        debug.log('Launching the job.', header='details')
        self._pop_none(kwargs)
        kwargs.update(start_data)
        job_started = client.post(endpoint, data=kwargs)

        # Get the job ID from the result.
        job_id = job_started.json()['id']

        # If returning json indicates any ignored fields, display it in
        # verbose mode.
        if job_started.text == '':
            ignored_fields = {}
        else:
            ignored_fields = job_started.json().get('ignored_fields', {})
        has_ignored_fields = False
        for key, value in ignored_fields.items():
            if value and value != '{}':
                if not has_ignored_fields:
                    debug.log('List of ignored fields on the server side:',
                              header='detail')
                    has_ignored_fields = True
                debug.log('{0}: {1}'.format(key, value))

        # Get some information about the running job to print
        result = self.status(pk=job_id, detail=True)
        result['changed'] = True

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

        return result
Esempio n. 46
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={})

        # 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'}
Esempio n. 47
0
    #job_data['extra_vars'] = json_extra_vars
    # TODO: nagios is breaking quotes, so don't try to interpret JSON for now
    if args.extra_vars:
      job_data['extra_vars'] = args.extra_vars
    else:
      job_data['extra_vars'] = "{nagios_no_extra_var: true }"
  except ValueError:
    error("The extra_vars parameter is not valid JSON.")

if(job_check.json()['ask_limit_on_launch'] and not args.limit):
  log_run("ERROR: job requires --limit")
  error("The job requires a list of hosts to limit the run.")
else:
  job_data['limit'] = args.limit

try:
  job_started = client.post('/job_templates/%s/launch/' % template_number, data=job_data)
  #print json.dumps(job_started.json(), indent=2)
  if(job_started.json()['id'] and job_started.json()['job']):
    job_number = job_started.json()['id']
    job_status = "STARTED"
    log_run("OK: job started")
    info("Tower job %s started." % job_number)
  else:
    job_status = "FAILED"
    log_run("ERROR: API call to start job failed")
    error("Could not start tower job: %s" % job_started['result_stdout'])
except exceptions.BadRequest:
  log_run("ERROR: bad request on API call -- URI[/job_templates/%s/launch/] DATA[%s]" % (template_number, job_data))
  error("There was a bad request on the API call -- URI[/job_templates/%s/launch/] DATA[%s]" % (template_number, job_data))
Esempio n. 48
0
    def launch(self,
               job_template,
               tags=None,
               monitor=False,
               timeout=None,
               no_input=True,
               extra_vars=None):
        """Launch a new job based on a job template.

        Creates a new job in Ansible Tower, immediately stats it, and
        returns back an ID in order for its status to be monitored.
        """
        # Get the job template from Ansible Tower.
        # This is used as the baseline for starting the job.
        jt_resource = get_resource('job_template')
        jt = jt_resource.get(job_template)

        # Update the job data by adding an automatically-generated job name,
        # and removing the ID.
        data = copy(jt)
        data['job_template'] = data.pop('id')
        data['name'] = '%s [invoked via. Tower CLI]' % data['name']
        if tags:
            data['job_tags'] = tags

        # If the job template requires prompting for extra variables,
        # do so (unless --no-input is set).
        if extra_vars:
            if hasattr(extra_vars, 'read'):
                extra_vars = extra_vars.read()
            data['extra_vars'] = extra_vars
        elif data.pop('ask_variables_on_launch', False) and not no_input:
            initial = data['extra_vars']
            initial = '\n'.join((
                '# Specify extra variables (if any) here.',
                '# Lines beginning with "#" are ignored.',
                initial,
            ))
            extra_vars = click.edit(initial) or ''
            extra_vars = '\n'.join(
                [i for i in extra_vars.split('\n') if not i.startswith('#')])
            data['extra_vars'] = extra_vars

        # In Tower 2.1 and later, we create the new job with
        # /job_templates/N/launch/; in Tower 2.0 and before, there is a two
        # step process of posting to /jobs/ and then /jobs/N/start/.
        supports_job_template_launch = False
        if 'launch' in jt['related']:
            supports_job_template_launch = True

        # Create the new job in Ansible Tower.
        start_data = {}
        if supports_job_template_launch:
            endpoint = '/job_templates/%d/launch/' % jt['id']
            if 'extra_vars' in data:
                start_data['extra_vars'] = data['extra_vars']
            if tags:
                start_data['job_tags'] = data['job_tags']
        else:
            debug.log('Creating the job.', header='details')
            job = client.post('/jobs/', data=data).json()
            job_id = job['id']
            endpoint = '/jobs/%d/start/' % job_id

        # There's a non-trivial chance that we are going to need some
        # additional information to start the job; in particular, many jobs
        # rely on passwords entered at run-time.
        #
        # If there are any such passwords on this job, ask for them now.

        debug.log('Asking for information necessary to start the job.',
                  header='details')
        job_start_info = client.get(endpoint).json()
        for password in job_start_info.get('passwords_needed_to_start', []):
            start_data[password] = getpass('Password for %s: ' % password)

        # Actually start the job.
        debug.log('Launching the job.', header='details')
        result = client.post(endpoint, start_data)

        # If this used the /job_template/N/launch/ route, get the job
        # ID from the result.
        if supports_job_template_launch:
            job_id = result.json()['job']

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

        # Return the job ID.
        return {
            'changed': True,
            'id': job_id,
        }
Esempio n. 49
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.

        =====API DOCS=====
        Update the given project.

        :param pk: Primary key of the project to be updated.
        :type pk: int
        :param monitor: Flag that if set, immediately calls ``monitor`` on the newly launched project update
                        rather than exiting with a success.
        :type monitor: bool
        :param wait: Flag that if set, monitor the status of the project 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 name: Name of the project to be updated if ``pk`` is not set.
        :type name: str
        :param organization: Primary key or name of the organization the project to be updated belonging to if
                             ``pk`` is not set.
        :type organization: str
        :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.CannotStartJob: When the project cannot be updated.

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

        project_update_id = result.json()['project_update']

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

        # Return the project update ID.
        return {
            'id': project_update_id,
            'changed': True,
        }