class Resource(models.Resource):
    cli_help = 'Manage organizations within Ansible Tower.'
    endpoint = '/organizations/'
    deprecated_methods = ['associate_project', 'disassociate_project']

    name = models.Field(unique=True)
    description = models.Field(required=False, display=False)
    users = models.ManyToManyField('user', method_name='')
    admins = models.ManyToManyField('user', method_name='admin')
    instance_groups = models.ManyToManyField('instance_group',
                                             method_name='ig')
Ejemplo n.º 2
0
    def test_method_docs(self):
        f = models.ManyToManyField('user')

        class Foo(models.BaseResource):
            admins = f
            endpoint = '/foos/'

        expect_text = """Associate an admin with this foo.

        =====API DOCS=====
        Associate an admin with this foo.

        :param foo: Primary key or name of the foo to associate to.
        :type foo: str
        :param user: Primary key or name of the user to be associated.
        :type user: str
        :returns: Dictionary of only one key "changed", which indicates whether the associate succeeded.
        :rtype: dict

        =====API DOCS=====
        """.strip(' ')

        self.assertEqual(Foo.associate_admin.__doc__.strip(' '), expect_text)
        self.assertEqual(
            Foo.disassociate_admin.__doc__.strip(' '),
            expect_text.replace('associate', 'disassociate').replace(
                'Associate', 'Disassociate'))
Ejemplo n.º 3
0
 def test_configure_model(self):
     f = models.ManyToManyField('user')
     self.assertEqual(f.res_name, None)
     self.assertEqual(f.relationship, None)
     f.configure_model({'endpoint': '/organizations/'}, 'auditors')
     self.assertEqual(f.res_name, 'organization')
     self.assertEqual(f.relationship, 'auditors')
Ejemplo n.º 4
0
    def test_resource_configuration(self):
        f = models.ManyToManyField('user')

        class Foo(models.BaseResource):
            admins = f
            endpoint = '/foos/'

        self.assertEqual(Foo.m2m_fields, [f])
        self.assertTrue(hasattr(Foo, 'associate_admin'))
        self.assertTrue(hasattr(Foo, 'disassociate_admin'))
Ejemplo n.º 5
0
class Resource(models.Resource):
    """A resource for teams."""
    cli_help = 'Manage teams within Ansible Tower.'
    endpoint = '/teams/'
    identity = ('organization', 'name')

    name = models.Field(unique=True)
    organization = models.Field(type=types.Related('organization'))
    description = models.Field(required=False, display=False)

    users = models.ManyToManyField('user', method_name='')
Ejemplo n.º 6
0
    def test_disassociate_method(self):
        f = models.ManyToManyField('user',
                                   res_name='organization',
                                   relationship='admins')
        method = f.disassociate_method

        class Resource:
            def _disassoc(self):
                pass

        res = Resource()
        with mock.patch.object(res, '_disassoc') as mock_disassoc:
            method(res, organization=1, user=2)
            mock_disassoc.assert_called_once_with('admins', 1, 2)
class Resource(models.Resource):
    """A resource for inventories."""
    cli_help = 'Manage inventory within Ansible Tower.'
    endpoint = '/inventories/'
    identity = ('organization', 'name')
    dependencies = ['organization']
    related = ['host', 'group', 'inventory_source']

    name = models.Field(unique=True)
    description = models.Field(required=False, display=False)
    organization = models.Field(type=types.Related('organization'))
    variables = models.Field(type=types.Variables(), required=False, display=False,
                             help_text='Inventory variables, use "@" to get from file.')
    kind = models.Field(type=click.Choice(['', 'smart']), required=False, display=False,
                        help_text='The kind field. Cannot be modified after created.')
    host_filter = models.Field(required=False, display=False,
                               help_text='The host_filter field. Only useful when kind=smart.')
    insights_credential = models.Field(display=False, required=False, type=types.Related('credential'))

    instance_groups = models.ManyToManyField('instance_group', method_name='ig')

    @resources.command(ignore_defaults=True)
    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()

    batch_update.format_freezer = 'json'
Ejemplo n.º 8
0
class Resource(models.SurveyResource):
    """A resource for workflow job templates."""
    cli_help = 'Manage workflow job templates.'
    endpoint = '/workflow_job_templates/'
    unified_job_type = '/workflow_jobs/'
    dependencies = ['organization']
    related = ['survey_spec', 'workflow_nodes', 'schedules', 'labels']
    workflow_node_types = ['success_nodes', 'failure_nodes', 'always_nodes']

    name = models.Field(unique=True)
    description = models.Field(required=False, display=False)
    extra_vars = models.Field(
        type=types.Variables(),
        required=False,
        display=False,
        multiple=True,
        help_text='Extra variables used by Ansible in YAML or key=value '
        'format. Use @ to get YAML from a file. Use the option '
        'multiple times to add multiple extra variables.')
    organization = models.Field(type=types.Related('organization'),
                                required=False)
    survey_enabled = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for job type on launch.')
    allow_simultaneous = models.Field(type=bool, required=False, display=False)
    survey_spec = models.Field(
        type=types.Variables(),
        required=False,
        display=False,
        help_text='On write commands, perform extra POST to the '
        'survey_spec endpoint.')

    labels = models.ManyToManyField('label', res_name='workflow')

    @staticmethod
    def _workflow_node_structure(node_results):
        '''
        Takes the list results from the API in `node_results` and
        translates this data into a dictionary organized in a
        human-readable heirarchial structure
        '''
        # Build list address translation, and create backlink lists
        node_list_pos = {}
        for i, node_result in enumerate(node_results):
            for rel in ['success', 'failure', 'always']:
                node_result['{0}_backlinks'.format(rel)] = []
            node_list_pos[node_result['id']] = i

        # Populate backlink lists
        for node_result in node_results:
            for rel in ['success', 'failure', 'always']:
                for sub_node_id in node_result['{0}_nodes'.format(rel)]:
                    j = node_list_pos[sub_node_id]
                    node_results[j]['{0}_backlinks'.format(rel)].append(
                        node_result['id'])

        # Find the root nodes
        root_nodes = []
        for node_result in node_results:
            is_root = True
            for rel in ['success', 'failure', 'always']:
                if node_result['{0}_backlinks'.format(rel)] != []:
                    is_root = False
                    break
            if is_root:
                root_nodes.append(node_result['id'])

        # Create network dictionary recursively from root nodes
        def branch_schema(node_id):
            i = node_list_pos[node_id]
            node_dict = node_results[i]
            ret_dict = {"id": node_id}
            for fd in NODE_STANDARD_FIELDS:
                val = node_dict.get(fd, None)
                if val is not None:
                    if fd == 'unified_job_template':
                        job_type = node_dict['summary_fields'][
                            'unified_job_template']['unified_job_type']
                        ujt_key = JOB_TYPES[job_type]
                        ret_dict[ujt_key] = val
                    else:
                        ret_dict[fd] = val
                for rel in ['success', 'failure', 'always']:
                    sub_node_id_list = node_dict['{0}_nodes'.format(rel)]
                    if len(sub_node_id_list) == 0:
                        continue
                    relationship_name = '{0}_nodes'.format(rel)
                    ret_dict[relationship_name] = []
                    for sub_node_id in sub_node_id_list:
                        ret_dict[relationship_name].append(
                            branch_schema(sub_node_id))
            return ret_dict

        schema_dict = []
        for root_node_id in root_nodes:
            schema_dict.append(branch_schema(root_node_id))
        return schema_dict

    def _get_schema(self, wfjt_id):
        """
        Returns a dictionary that represents the node network of the
        workflow job template
        """
        node_res = get_resource('node')
        node_results = node_res.list(workflow_job_template=wfjt_id,
                                     all_pages=True)['results']
        return self._workflow_node_structure(node_results)

    @resources.command(use_fields_as_options=False)
    @click.argument('wfjt', type=types.Related('workflow'))
    @click.argument('node_network', type=types.Variables(), required=False)
    def schema(self, wfjt, node_network=None):
        """
        Convert YAML/JSON content into workflow node objects if
        node_network param is given.
        If not, print a YAML representation of the node network.

        =====API DOCS=====
        Convert YAML/JSON content into workflow node objects if ``node_network`` param is given. If not,
        print a YAML representation of the node network.

        :param wfjt: Primary key or name of the workflow job template to run schema against.
        :type wfjt: str
        :param node_network: JSON- or YAML-formatted string representing the topology of the workflow job
                             template be updated to.
        :type node_network: str
        :returns: The latest topology (possibly after modification) of the workflow job template.
        :rtype: dict

        =====API DOCS=====
        """
        existing_network = self._get_schema(wfjt)
        if not isinstance(existing_network, list):
            existing_network = []
        if node_network is None:
            if settings.format == 'human':
                settings.format = 'yaml'
            return existing_network

        if hasattr(node_network, 'read'):
            node_network = node_network.read()
        node_network = string_to_dict(node_network,
                                      allow_kv=False,
                                      require_dict=False)
        if not isinstance(node_network, list):
            node_network = []

        _update_workflow(
            [TreeNode(x, wfjt, include_id=True) for x in existing_network],
            [TreeNode(x, wfjt) for x in node_network])

        if settings.format == 'human':
            settings.format = 'yaml'
        return self._get_schema(wfjt)

    @resources.command(use_fields_as_options=False)
    @click.option('--workflow', type=types.Related('workflow'))
    @click.option('--notification-template',
                  type=types.Related('notification_template'))
    @click.option('--status',
                  type=click.Choice(['any', 'error', 'success']),
                  required=False,
                  default='any',
                  help='Specify job run status'
                  ' of job template to relate to.')
    def associate_notification_template(self, workflow, notification_template,
                                        status):
        """Associate a notification template from this workflow.

        =====API DOCS=====
        Associate a notification template from this workflow job template.

        :param workflow: The workflow job template to associate to.
        :type workflow: str
        :param notification_template: The notification template to be associated.
        :type notification_template: str
        :param status: type of notification this notification template should be associated to.
        :type status: str
        :returns: Dictionary of only one key "changed", which indicates whether the association succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._assoc('notification_templates_%s' % status, workflow,
                           notification_template)

    @resources.command(use_fields_as_options=False)
    @click.option('--workflow', type=types.Related('workflow'))
    @click.option('--notification-template',
                  type=types.Related('notification_template'))
    @click.option('--status',
                  type=click.Choice(['any', 'error', 'success']),
                  required=False,
                  default='any',
                  help='Specify job run status'
                  ' of job template to relate to.')
    def disassociate_notification_template(self, workflow,
                                           notification_template, status):
        """Disassociate a notification template from this workflow.

        =====API DOCS=====
        Disassociate a notification template from this workflow job template.

        :param job_template: The workflow job template to disassociate from.
        :type job_template: str
        :param notification_template: The notification template to be disassociated.
        :type notification_template: str
        :param status: type of notification this notification template should be disassociated from.
        :type status: str
        :returns: Dictionary of only one key "changed", which indicates whether the disassociation succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._disassoc('notification_templates_%s' % status, workflow,
                              notification_template)
class Resource(models.Resource):
    """A resource for schedules."""
    cli_help = 'Manage schedules within Ansible Tower.'
    endpoint = '/schedules/'

    # General fields.
    name = models.Field(unique=True)
    description = models.Field(required=False, display=False)

    # Unified jt fields. note these fields will only be used during creation.
    # Plus, one and only one field should be provided.
    job_template = models.Field(type=types.Related('job_template'),
                                required=False,
                                display=False)
    inventory_source = models.Field(type=types.Related('inventory_source'),
                                    required=False,
                                    display=False)
    project = models.Field(type=types.Related('project'),
                           required=False,
                           display=False)

    # Schedule-specific fields.
    unified_job_template = models.Field(required=False,
                                        type=int,
                                        help_text='Integer used to display'
                                        ' unified job template in result, '
                                        'Do not use it for create/'
                                        'modify.')
    enabled = models.Field(required=False,
                           type=click.BOOL,
                           default=True,
                           help_text='Whether this schedule will be used.',
                           show_default=True)
    rrule = models.Field(required=False,
                         display=False,
                         help_text='Schedule rules specifications which is'
                         ' less than 255 characters.')

    # Prompts
    extra_data = models.Field(type=types.Variables(),
                              required=False,
                              display=False,
                              help_text='Extra data for '
                              'schedule rules in the form of a .json file.')
    inventory = models.Field(type=types.Related('inventory'),
                             required=False,
                             display=False)
    credentials = models.ManyToManyField('credential')
    job_type = models.Field(required=False, display=False)
    job_tags = models.Field(required=False, display=False)
    skip_tags = models.Field(required=False, display=False)
    limit = models.Field(required=False, display=False)
    diff_mode = models.Field(type=bool, required=False, display=False)
    verbosity = models.Field(
        display=False,
        type=types.MappedChoice([
            (0, 'default'),
            (1, 'verbose'),
            (2, 'more_verbose'),
            (3, 'debug'),
            (4, 'connection'),
            (5, 'winrm'),
        ]),
        required=False,
    )

    def _get_patch_url(self, url, pk):
        urlTokens = url.split('/')
        if len(urlTokens) > 3:
            # reconstruct url to prevent a rare corner case where resources
            # cannot be constructed independently. Open to modification if
            # API convention changes.
            url = '/'.join(urlTokens[:1] + urlTokens[-2:])
        return super(Resource, self)._get_patch_url(url, pk)
class Resource(models.Resource):
    """A resource for credentials."""
    cli_help = 'Manage hosts belonging to a group within an inventory.'
    endpoint = '/hosts/'
    identity = ('inventory', 'name')

    name = models.Field(unique=True)
    description = models.Field(required=False, display=False)
    inventory = models.Field(type=types.Related('inventory'))
    enabled = models.Field(type=bool, required=False)
    variables = models.Field(
        type=types.Variables(),
        required=False,
        display=False,
        help_text='Host variables, use "@" to get from file.')
    insights_system_id = models.Field(required=False, display=False)

    groups = models.ManyToManyField('group', method_name='')

    @resources.command(ignore_defaults=True, no_args_is_help=False)
    @click.option('--group',
                  type=types.Related('group'),
                  help='List hosts that are children of this group.')
    @click.option('--host-filter',
                  help='List hosts filtered by this fact search query string.')
    def list(self, group=None, host_filter=None, **kwargs):
        """Return a list of hosts.

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

        :param group: Primary key or name of the group whose hosts will be listed.
        :type group: str
        :param all_pages: Flag that if set, collect all pages of content from the API when returning results.
        :type all_pages: bool
        :param page: The page to show. Ignored if all_pages is set.
        :type page: int
        :param query: Contains 2-tuples used as query parameters to filter resulting resource objects.
        :type query: list
        :param `**kwargs`: Keyword arguments list of available fields used for searching resource objects.
        :returns: A JSON object containing details of all resource objects returned by Tower backend.
        :rtype: dict

        =====API DOCS=====
        """
        if group:
            kwargs['query'] = kwargs.get('query',
                                         ()) + (('groups__in', group), )
        if host_filter:
            kwargs['query'] = kwargs.get('query',
                                         ()) + (('host_filter', host_filter), )
        return super(Resource, self).list(**kwargs)

    @resources.command(ignore_defaults=True)
    def list_facts(self, pk=None, **kwargs):
        """Return a JSON object of all available facts of the given host.

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

        =====API DOCS=====
        List all available facts of the given host.

        :param pk: Primary key of the target host.
        :type pk: int
        :param `**kwargs`: Keyword arguments list of available fields used for searching resource objects.
        :returns: A JSON object of all available facts of the given host.
        :rtype: dict
        =====API DOCS=====
        """
        res = self.get(pk=pk, **kwargs)
        url = self.endpoint + '%d/%s/' % (res['id'], 'ansible_facts')
        return client.get(url, params={}).json()

    list_facts.format_freezer = 'json'

    @resources.command(ignore_defaults=True)
    def insights(self, pk=None, **kwargs):
        """Return a JSON object of host insights.

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

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

        :param pk: Primary key of the target host.
        :type pk: int
        :param `**kwargs`: Keyword arguments list of available fields used for searching resource objects.
        :returns: A JSON object of host insights.
        :rtype: dict
        =====API DOCS=====
        """
        res = self.get(pk=pk, **kwargs)
        url = self.endpoint + '%d/%s/' % (res['id'], 'insights')
        return client.get(url, params={}).json()

    insights.format_freezer = 'json'
Ejemplo n.º 11
0
class Resource(models.Resource):
    """A resource for workflow nodes."""
    cli_help = 'Manage nodes inside of a workflow job template.'
    endpoint = '/workflow_job_template_nodes/'
    identity = ('id', )

    workflow_job_template = models.Field(key='-W',
                                         type=types.Related('workflow'))
    unified_job_template = models.Field(required=False)

    # Prompts
    extra_data = models.Field(type=types.Variables(),
                              required=False,
                              display=False,
                              help_text='Extra data for '
                              'schedule rules in the form of a .json file.')
    inventory = models.Field(type=types.Related('inventory'),
                             required=False,
                             display=False)
    credential = models.Field(type=types.Related('credential'),
                              required=False,
                              display=False)
    credentials = models.ManyToManyField('credential')
    job_type = models.Field(required=False, display=False)
    job_tags = models.Field(required=False, display=False)
    skip_tags = models.Field(required=False, display=False)
    limit = models.Field(required=False, display=False)
    diff_mode = models.Field(type=bool, required=False, display=False)
    verbosity = models.Field(
        display=False,
        type=types.MappedChoice([
            (0, 'default'),
            (1, 'verbose'),
            (2, 'more_verbose'),
            (3, 'debug'),
            (4, 'connection'),
            (5, 'winrm'),
        ]),
        required=False,
    )

    def __new__(cls, *args, **kwargs):
        for attr in ['create', 'modify', 'list']:
            setattr(cls, attr, unified_job_template_options(getattr(cls,
                                                                    attr)))
        return super(Resource, cls).__new__(cls, *args, **kwargs)

    @staticmethod
    def _forward_rel_name(rel):
        return '{0}_nodes'.format(rel)

    @staticmethod
    def _reverse_rel_name(rel):
        return 'workflowjobtemplatenodes_{0}'.format(rel)

    def _parent_filter(self, parent, relationship, **kwargs):
        """
        Returns filtering parameters to limit a search to the children
        of a particular node by a particular relationship.
        """
        if parent is None or relationship is None:
            return {}
        parent_filter_kwargs = {}
        query_params = ((self._reverse_rel_name(relationship), parent), )
        parent_filter_kwargs['query'] = query_params
        if kwargs.get('workflow_job_template', None) is None:
            parent_data = self.read(pk=parent)['results'][0]
            parent_filter_kwargs['workflow_job_template'] = parent_data[
                'workflow_job_template']
        return parent_filter_kwargs

    @unified_job_template_options
    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]

    def _assoc_or_create(self, relationship, parent, child, **kwargs):
        if child is None:
            child_data = self._get_or_create_child(parent, relationship,
                                                   **kwargs)
            return child_data
        return self._assoc(self._forward_rel_name(relationship), parent, child)

    @resources.command
    @unified_job_template_options
    @click.argument('parent', type=types.Related('node'))
    @click.argument('child', type=types.Related('node'), required=False)
    def associate_success_node(self, parent, child=None, **kwargs):
        """Add a node to run on success.

        =====API DOCS=====
        Add a node to run on success.

        :param parent: Primary key of parent node to associate success node to.
        :type parent: int
        :param child: Primary key of child node to be associated.
        :type child: int
        :param `**kwargs`: Fields used to create child node if ``child`` is not provided.
        :returns: Dictionary of only one key "changed", which indicates whether the association succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._assoc_or_create('success', parent, child, **kwargs)

    @resources.command(use_fields_as_options=False)
    @click.argument('parent', type=types.Related('node'))
    @click.argument('child', type=types.Related('node'))
    def disassociate_success_node(self, parent, child):
        """Remove success node.
        The resulatant 2 nodes will both become root nodes.

        =====API DOCS=====
        Remove success node.

        :param parent: Primary key of parent node to disassociate success node from.
        :type parent: int
        :param child: Primary key of child node to be disassociated.
        :type child: int
        :returns: Dictionary of only one key "changed", which indicates whether the disassociation succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._disassoc(self._forward_rel_name('success'), parent, child)

    @resources.command
    @unified_job_template_options
    @click.argument('parent', type=types.Related('node'))
    @click.argument('child', type=types.Related('node'), required=False)
    def associate_failure_node(self, parent, child=None, **kwargs):
        """Add a node to run on failure.

        =====API DOCS=====
        Add a node to run on failure.

        :param parent: Primary key of parent node to associate failure node to.
        :type parent: int
        :param child: Primary key of child node to be associated.
        :type child: int
        :param `**kwargs`: Fields used to create child node if ``child`` is not provided.
        :returns: Dictionary of only one key "changed", which indicates whether the association succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._assoc_or_create('failure', parent, child, **kwargs)

    @resources.command(use_fields_as_options=False)
    @click.argument('parent', type=types.Related('node'))
    @click.argument('child', type=types.Related('node'))
    def disassociate_failure_node(self, parent, child):
        """Remove a failure node link.
        The resulatant 2 nodes will both become root nodes.

        =====API DOCS=====
        Remove a failure node link.

        :param parent: Primary key of parent node to disassociate failure node from.
        :type parent: int
        :param child: Primary key of child node to be disassociated.
        :type child: int
        :returns: Dictionary of only one key "changed", which indicates whether the disassociation succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._disassoc(self._forward_rel_name('failure'), parent, child)

    @resources.command
    @unified_job_template_options
    @click.argument('parent', type=types.Related('node'))
    @click.argument('child', type=types.Related('node'), required=False)
    def associate_always_node(self, parent, child=None, **kwargs):
        """Add a node to always run after the parent is finished.

        =====API DOCS=====
        Add a node to always run after the parent is finished.

        :param parent: Primary key of parent node to associate always node to.
        :type parent: int
        :param child: Primary key of child node to be associated.
        :type child: int
        :param `**kwargs`: Fields used to create child node if ``child`` is not provided.
        :returns: Dictionary of only one key "changed", which indicates whether the association succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._assoc_or_create('always', parent, child, **kwargs)

    @resources.command(use_fields_as_options=False)
    @click.argument('parent', type=types.Related('node'))
    @click.argument('child', type=types.Related('node'))
    def disassociate_always_node(self, parent, child):
        """Remove an always node link.
        The resultant 2 nodes will both become root nodes.

        =====API DOCS=====
        Remove an always node link.

        :param parent: Primary key of parent node to disassociate always node from.
        :type parent: int
        :param child: Primary key of child node to be disassociated.
        :type child: int
        :returns: Dictionary of only one key "changed", which indicates whether the disassociation succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._disassoc(self._forward_rel_name('always'), parent, child)
class Resource(models.SurveyResource):
    """A resource for job templates."""
    cli_help = 'Manage job templates.'
    endpoint = '/job_templates/'
    dependencies = ['inventory', 'credential', 'project', 'vault_credential']
    related = [
        'survey_spec', 'notification_templates', 'extra_credentials',
        'schedules'
    ]

    name = models.Field(unique=True)
    description = models.Field(required=False, display=False)
    job_type = models.Field(
        required=False,
        display=False,
        type=click.Choice(['run', 'check']),
    )
    inventory = models.Field(type=types.Related('inventory'), required=False)
    project = models.Field(type=types.Related('project'))
    playbook = models.Field()
    credential = models.Field(display=False,
                              required=False,
                              type=types.Related('credential'))
    vault_credential = models.Field(type=types.Related('credential'),
                                    required=False,
                                    display=False)
    forks = models.Field(type=int, required=False, display=False)
    limit = models.Field(required=False, display=False)
    verbosity = models.Field(
        display=False,
        type=types.MappedChoice([
            (0, 'default'),
            (1, 'verbose'),
            (2, 'more_verbose'),
            (3, 'debug'),
            (4, 'connection'),
            (5, 'winrm'),
        ]),
        required=False,
    )
    extra_vars = models.Field(
        type=types.Variables(),
        required=False,
        display=False,
        multiple=True,
        help_text='Extra variables used by Ansible in YAML or key=value '
        'format. Use @ to get YAML from a file.')
    job_tags = models.Field(required=False, display=False)
    force_handlers = models.Field(type=bool, required=False, display=False)
    skip_tags = models.Field(required=False, display=False)
    start_at_task = models.Field(required=False, display=False)
    timeout = models.Field(
        type=int,
        required=False,
        display=False,
        help_text=
        'The amount of time (in seconds) to run before the task is canceled.')
    use_fact_cache = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='If enabled, Tower will act as an Ansible Fact Cache Plugin;'
        ' persisting facts at the end of a playbook run to the database'
        ' and caching facts for use by Ansible.')
    host_config_key = models.Field(
        required=False,
        display=False,
        help_text='Allow Provisioning Callbacks using this host config key')
    ask_diff_mode_on_launch = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Ask diff mode on launch.')
    ask_variables_on_launch = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for extra_vars on launch.')
    ask_limit_on_launch = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for host limits on launch.')
    ask_tags_on_launch = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for job tags on launch.')
    ask_skip_tags_on_launch = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for tags to skip on launch.')
    ask_job_type_on_launch = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for job type on launch.')
    ask_verbosity_on_launch = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for verbosity on launch.')
    ask_inventory_on_launch = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for inventory on launch.')
    ask_credential_on_launch = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for machine credential on launch.')
    survey_enabled = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='Prompt user for job type on launch.')
    become_enabled = models.Field(type=bool, required=False, display=False)
    diff_mode = models.Field(
        type=bool,
        required=False,
        display=False,
        help_text='If enabled, textual changes made to any templated files on'
        ' the host are shown in the standard output.')
    allow_simultaneous = models.Field(type=bool, required=False, display=False)

    survey_spec = models.Field(
        type=types.Variables(),
        required=False,
        display=False,
        help_text='On write commands, perform extra POST to the '
        'survey_spec endpoint.')

    labels = models.ManyToManyField('label')
    instance_groups = models.ManyToManyField('instance_group',
                                             method_name='ig')

    def write(self, *args, **kwargs):
        # Provide a default value for job_type, but only in creation of JT
        if (kwargs.get('create_on_missing', False)
                and (not kwargs.get('job_type', None))):
            kwargs['job_type'] = 'run'
        return super(Resource, self).write(*args, **kwargs)

    @resources.command(use_fields_as_options=False)
    @click.option('--job-template', type=types.Related('job_template'))
    @click.option('--credential', type=types.Related('credential'))
    def associate_credential(self, job_template, credential):
        """Associate a credential with this job template.

        =====API DOCS=====
        Associate a credential with this job template.

        :param job_template: The job template to associate to.
        :type job_template: str
        :param credential: The credential to be associated.
        :type credential: str
        :returns: Dictionary of only one key "changed", which indicates whether the association succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._assoc('extra_credentials', job_template, credential)

    @resources.command(use_fields_as_options=False)
    @click.option('--job-template', type=types.Related('job_template'))
    @click.option('--credential', type=types.Related('credential'))
    def disassociate_credential(self, job_template, credential):
        """Disassociate a credential with this job template.

        =====API DOCS=====
        Disassociate a credential from this job template.

        :param job_template: The job template to disassociate fom.
        :type job_template: str
        :param credential: The credential to be disassociated.
        :type credential: str
        :returns: Dictionary of only one key "changed", which indicates whether the disassociation succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._disassoc('extra_credentials', job_template, credential)

    @resources.command(use_fields_as_options=False)
    @click.option('--job-template', type=types.Related('job_template'))
    @click.option('--notification-template',
                  type=types.Related('notification_template'))
    @click.option('--status',
                  type=click.Choice(['any', 'error', 'success']),
                  required=False,
                  default='any',
                  help='Specify job run status'
                  ' of job template to relate to.')
    def associate_notification_template(self, job_template,
                                        notification_template, status):
        """Associate a notification template from this job template.

        =====API DOCS=====
        Associate a notification template from this job template.

        :param job_template: The job template to associate to.
        :type job_template: str
        :param notification_template: The notification template to be associated.
        :type notification_template: str
        :param status: type of notification this notification template should be associated to.
        :type status: str
        :returns: Dictionary of only one key "changed", which indicates whether the association succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._assoc('notification_templates_%s' % status, job_template,
                           notification_template)

    @resources.command(use_fields_as_options=False)
    @click.option('--job-template', type=types.Related('job_template'))
    @click.option('--notification-template',
                  type=types.Related('notification_template'))
    @click.option('--status',
                  type=click.Choice(['any', 'error', 'success']),
                  required=False,
                  default='any',
                  help='Specify job run status'
                  ' of job template to relate to.')
    def disassociate_notification_template(self, job_template,
                                           notification_template, status):
        """Disassociate a notification template from this job template.

        =====API DOCS=====
        Disassociate a notification template from this job template.

        :param job_template: The job template to disassociate from.
        :type job_template: str
        :param notification_template: The notification template to be disassociated.
        :type notification_template: str
        :param status: type of notification this notification template should be disassociated from.
        :type status: str
        :returns: Dictionary of only one key "changed", which indicates whether the disassociation succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._disassoc('notification_templates_%s' % status,
                              job_template, notification_template)

    @resources.command(use_fields_as_options=('extra_vars'))
    @click.option('--host-config-key',
                  help='Job-template-specific string used to authenticate '
                  'host during provisioning callback.')
    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}
Ejemplo n.º 13
0
 def test_method_name(self):
     f = models.ManyToManyField('user',
                                relationship='auditors',
                                res_name='organization')
     self.assertEqual(f.associate_method_name, 'associate_auditor')
     self.assertEqual(f.disassociate_method_name, 'disassociate_auditor')
class Resource(models.Resource):
    cli_help = 'Manage organizations within Ansible Tower.'
    endpoint = '/organizations/'
    deprecated_methods = ['associate_project', 'disassociate_project']

    name = models.Field(unique=True)
    description = models.Field(required=False, display=False)
    users = models.ManyToManyField('user', method_name='')
    admins = models.ManyToManyField('user', method_name='admin')
    instance_groups = models.ManyToManyField('instance_group', method_name='ig')
    custom_virtualenv = models.Field(required=False, display=False)

    @resources.command(use_fields_as_options=False)
    @click.option('--organization', type=types.Related('organization'))
    @click.option('--notification-template',
                  type=types.Related('notification_template'))
    @click.option('--status', type=click.Choice(['any', 'error', 'success']),
                  required=False, default='any', help='Specify job run status'
                  ' of inventory_sync to relate to.')
    def associate_notification_template(self, organization,
                                        notification_template, status):
        """Associate a notification template from this organization.

        =====API DOCS=====
        Associate a notification template from this organization.

        :param organization: The organization to associate to.
        :type organization: str
        :param notification_template: The notification template to be associated.
        :type notification_template: str
        :param status: type of notification this notification template should be associated to.
        :type status: str
        :returns: Dictionary of only one key "changed", which indicates whether the association succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._assoc('notification_templates_%s' % status,
                           organization, notification_template)

    @resources.command(use_fields_as_options=False)
    @click.option('--organization', type=types.Related('organization'))
    @click.option('--notification-template',
                  type=types.Related('notification_template'))
    @click.option('--status', type=click.Choice(['any', 'error', 'success']),
                  required=False, default='any', help='Specify job run status'
                  ' of inventory_sync to relate to.')
    def disassociate_notification_template(self, organization,
                                           notification_template, status):
        """Disassociate a notification template from this organization.

        =====API DOCS=====
        Disassociate a notification template from this organization.

        :param organization: The organization to disassociate from.
        :type organization: str
        :param notification_template: The notification template to be disassociated.
        :type notification_template: str
        :param status: type of notification this notification template should be disassociated from.
        :type status: str
        :returns: Dictionary of only one key "changed", which indicates whether the disassociation succeeded.
        :rtype: dict

        =====API DOCS=====
        """
        return self._disassoc('notification_templates_%s' % status,
                              organization, notification_template)