2016-10-27 09:25:44 -04:00
|
|
|
# Copyright 2016, Ansible by Red Hat
|
|
|
|
|
# Alan Rominger <arominge@redhat.com>
|
|
|
|
|
#
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
|
|
from __future__ import absolute_import, unicode_literals
|
|
|
|
|
|
2017-07-06 17:22:54 -04:00
|
|
|
from tower_cli import models, resources, exceptions
|
|
|
|
|
from tower_cli.cli import types
|
2017-03-01 12:56:15 -05:00
|
|
|
from tower_cli.utils.resource_decorators import unified_job_template_options
|
2017-07-06 17:22:54 -04:00
|
|
|
from tower_cli.utils import debug
|
2017-03-01 21:55:11 -05:00
|
|
|
from tower_cli.api import client
|
2017-03-01 12:56:15 -05:00
|
|
|
|
|
|
|
|
import click
|
2016-10-27 09:25:44 -04:00
|
|
|
|
|
|
|
|
|
2017-03-01 21:12:35 -05:00
|
|
|
NODE_STANDARD_FIELDS = [
|
|
|
|
|
'unified_job_template', 'inventory', 'credential', 'job_type',
|
|
|
|
|
'job_tags', 'skip_tags', 'limit'
|
|
|
|
|
]
|
|
|
|
|
JOB_TYPES = {
|
|
|
|
|
'job': 'job_template',
|
|
|
|
|
'project_update': 'project',
|
2019-01-18 21:00:11 -05:00
|
|
|
'inventory_update': 'inventory_source',
|
|
|
|
|
'workflow_job': 'workflow'
|
2017-03-01 21:12:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-10-27 09:25:44 -04:00
|
|
|
class Resource(models.Resource):
|
2017-08-08 11:44:45 -04:00
|
|
|
"""A resource for workflow nodes."""
|
2016-10-27 09:25:44 -04:00
|
|
|
cli_help = 'Manage nodes inside of a workflow job template.'
|
|
|
|
|
endpoint = '/workflow_job_template_nodes/'
|
|
|
|
|
identity = ('id',)
|
|
|
|
|
|
|
|
|
|
workflow_job_template = models.Field(
|
2017-03-02 21:27:26 -05:00
|
|
|
key='-W', type=types.Related('workflow'))
|
2017-03-01 15:57:06 -05:00
|
|
|
unified_job_template = models.Field(required=False)
|
2017-11-21 15:00:30 -05:00
|
|
|
|
|
|
|
|
# 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.')
|
2016-10-27 09:25:44 -04:00
|
|
|
inventory = models.Field(
|
|
|
|
|
type=types.Related('inventory'), required=False, display=False)
|
|
|
|
|
credential = models.Field(
|
|
|
|
|
type=types.Related('credential'), required=False, display=False)
|
2019-04-23 18:16:24 +05:30
|
|
|
credentials = models.ManyToManyField('credential', res_name='node')
|
2016-10-27 09:25:44 -04:00
|
|
|
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)
|
2017-11-21 15:00:30 -05:00
|
|
|
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,
|
|
|
|
|
)
|
2017-03-01 12:56:15 -05:00
|
|
|
|
2017-03-02 21:27:26 -05:00
|
|
|
def __new__(cls, *args, **kwargs):
|
2019-03-11 10:21:53 +03:00
|
|
|
for attr_name in ['create', 'modify', 'list']:
|
|
|
|
|
|
|
|
|
|
attr = getattr(cls, attr_name)
|
|
|
|
|
if getattr(attr, '__decorator__', None) == 'unified_job_template_options':
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
wrapped_func = unified_job_template_options(attr)
|
|
|
|
|
wrapped_func.__decorator__ = 'unified_job_template_options'
|
|
|
|
|
|
|
|
|
|
setattr(cls, attr_name, wrapped_func)
|
|
|
|
|
|
2017-03-02 21:27:26 -05:00
|
|
|
return super(Resource, cls).__new__(cls, *args, **kwargs)
|
2017-03-01 15:57:06 -05:00
|
|
|
|
2017-03-02 09:38:46 -05:00
|
|
|
@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
|
2017-03-01 15:57:06 -05:00
|
|
|
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')
|
2017-03-02 09:38:46 -05:00
|
|
|
kwargs.update(self._parent_filter(parent, relationship, **kwargs))
|
2017-03-01 15:57:06 -05:00
|
|
|
response = self.read(
|
2017-03-02 09:38:46 -05:00
|
|
|
fail_on_no_results=False, fail_on_multiple_results=False, **kwargs)
|
2017-03-01 15:57:06 -05:00
|
|
|
if len(response['results']) == 0:
|
2017-03-02 09:38:46 -05:00
|
|
|
debug.log('Creating new workflow node.', header='details')
|
|
|
|
|
return client.post(self.endpoint, data=kwargs).json()
|
2017-03-01 15:57:06 -05:00
|
|
|
else:
|
|
|
|
|
return response['results'][0]
|
|
|
|
|
|
2017-03-01 21:55:11 -05:00
|
|
|
def _assoc_or_create(self, relationship, parent, child, **kwargs):
|
|
|
|
|
if child is None:
|
2017-08-08 11:44:45 -04:00
|
|
|
child_data = self._get_or_create_child(parent, relationship, **kwargs)
|
2017-03-01 23:27:00 -05:00
|
|
|
return child_data
|
2017-08-08 11:44:45 -04:00
|
|
|
return self._assoc(self._forward_rel_name(relationship), parent, child)
|
2017-03-01 21:55:11 -05:00
|
|
|
|
2017-03-01 15:57:06 -05:00
|
|
|
@resources.command
|
2017-03-01 12:56:15 -05:00
|
|
|
@unified_job_template_options
|
|
|
|
|
@click.argument('parent', type=types.Related('node'))
|
2017-03-01 21:55:11 -05:00
|
|
|
@click.argument('child', type=types.Related('node'), required=False)
|
2017-03-01 12:56:15 -05:00
|
|
|
def associate_success_node(self, parent, child=None, **kwargs):
|
2017-08-08 11:44:45 -04:00
|
|
|
"""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=====
|
|
|
|
|
"""
|
2017-03-02 09:38:46 -05:00
|
|
|
return self._assoc_or_create('success', parent, child, **kwargs)
|
2017-03-01 21:55:11 -05:00
|
|
|
|
2017-03-02 21:27:26 -05:00
|
|
|
@resources.command(use_fields_as_options=False)
|
2017-03-01 21:55:11 -05:00
|
|
|
@click.argument('parent', type=types.Related('node'))
|
2017-03-02 21:27:26 -05:00
|
|
|
@click.argument('child', type=types.Related('node'))
|
|
|
|
|
def disassociate_success_node(self, parent, child):
|
2017-03-01 21:55:11 -05:00
|
|
|
"""Remove success node.
|
2017-08-08 11:44:45 -04:00
|
|
|
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=====
|
|
|
|
|
"""
|
2017-03-02 09:38:46 -05:00
|
|
|
return self._disassoc(
|
|
|
|
|
self._forward_rel_name('success'), parent, child)
|
2017-03-01 15:57:06 -05:00
|
|
|
|
|
|
|
|
@resources.command
|
|
|
|
|
@unified_job_template_options
|
|
|
|
|
@click.argument('parent', type=types.Related('node'))
|
2017-03-01 21:55:11 -05:00
|
|
|
@click.argument('child', type=types.Related('node'), required=False)
|
2017-03-01 15:57:06 -05:00
|
|
|
def associate_failure_node(self, parent, child=None, **kwargs):
|
2017-08-08 11:44:45 -04:00
|
|
|
"""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=====
|
|
|
|
|
"""
|
2017-03-02 09:38:46 -05:00
|
|
|
return self._assoc_or_create('failure', parent, child, **kwargs)
|
2017-03-01 21:55:11 -05:00
|
|
|
|
2017-03-02 21:27:26 -05:00
|
|
|
@resources.command(use_fields_as_options=False)
|
2017-03-01 21:55:11 -05:00
|
|
|
@click.argument('parent', type=types.Related('node'))
|
2017-03-02 21:27:26 -05:00
|
|
|
@click.argument('child', type=types.Related('node'))
|
|
|
|
|
def disassociate_failure_node(self, parent, child):
|
2017-03-01 21:55:11 -05:00
|
|
|
"""Remove a failure node link.
|
2017-08-08 11:44:45 -04:00
|
|
|
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=====
|
|
|
|
|
"""
|
2017-03-02 09:38:46 -05:00
|
|
|
return self._disassoc(
|
|
|
|
|
self._forward_rel_name('failure'), parent, child)
|
2017-03-01 15:57:06 -05:00
|
|
|
|
|
|
|
|
@resources.command
|
|
|
|
|
@unified_job_template_options
|
|
|
|
|
@click.argument('parent', type=types.Related('node'))
|
2017-03-01 21:55:11 -05:00
|
|
|
@click.argument('child', type=types.Related('node'), required=False)
|
2017-03-01 15:57:06 -05:00
|
|
|
def associate_always_node(self, parent, child=None, **kwargs):
|
2017-08-08 11:44:45 -04:00
|
|
|
"""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=====
|
|
|
|
|
"""
|
2017-03-02 09:38:46 -05:00
|
|
|
return self._assoc_or_create('always', parent, child, **kwargs)
|
2017-03-01 21:55:11 -05:00
|
|
|
|
2017-03-02 21:27:26 -05:00
|
|
|
@resources.command(use_fields_as_options=False)
|
2017-03-01 21:55:11 -05:00
|
|
|
@click.argument('parent', type=types.Related('node'))
|
2017-03-02 21:27:26 -05:00
|
|
|
@click.argument('child', type=types.Related('node'))
|
|
|
|
|
def disassociate_always_node(self, parent, child):
|
2017-08-08 11:44:45 -04:00
|
|
|
"""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=====
|
|
|
|
|
"""
|
2017-03-02 09:38:46 -05:00
|
|
|
return self._disassoc(
|
|
|
|
|
self._forward_rel_name('always'), parent, child)
|