Repository: ansible/tower-cli
Pull request #384 (2017-09-29 19:25:44)

add resources for project/inv updates
Connect #343

This isn't perfect, but it will help a lot.

I've tested this with the stdout, delete, and cancel commands. Before, we had no access to these, and all of the logic already works.

I also changed how we are returning responses for basic project and inventory updates. For job launches we return the id, so I want to be consistent with that. But of course, launching a job from the job command has a congruency that launching from the template does not. So it's still weird, but we should be returning the job id on every launch as a best-possible practice.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13525113/badge)](https://coveralls.io/builds/13525113)

Coverage decreased (-1.02%) to 89.986% when pulling **a247a1f342a898d057d50bfe91d167be1c98c42b on AlanCoding:unified_jobs** into **81ba8300d694f91207ca7105bc975d9c162c664f on ansible:master**.


Patch:
diff --git a/tower_cli/constants.py b/tower_cli/constants.py
index afb1906..ccae43a 100644
--- a/tower_cli/constants.py
+++ b/tower_cli/constants.py
@@ -14,3 +14,18 @@
 
 
 CUR_API_VERSION = 'v2'
+
+LAUNCH_TYPE_CHOICES = [
+    'manual', 'relaunch', 'relaunch', 'callback',
+    'scheduled', 'dependency', 'workflow', 'sync', 'scm'
+]
+
+STATUS_CHOICES = [
+    'new', 'pending', 'waiting', 'running', 'successful',
+    'failed', 'error', 'canceled'
+]
+
+INVENTORY_SOURCE_CHOICES = [
+    '', 'file', 'scm', 'ec2', 'vmware', 'gce', 'azure', 'azure_rm', 'openstack',
+    'satellite6', 'cloudforms', 'custom'
+]
diff --git a/tower_cli/resources/inventory_source.py b/tower_cli/resources/inventory_source.py
index 314da7a..2150a6d 100644
--- a/tower_cli/resources/inventory_source.py
+++ b/tower_cli/resources/inventory_source.py
@@ -16,6 +16,7 @@
 import click
 
 from tower_cli import models, resources, exceptions as exc
+from tower_cli.constants import INVENTORY_SOURCE_CHOICES
 from tower_cli.api import client
 from tower_cli.cli import types
 from tower_cli.utils import debug
@@ -33,8 +34,7 @@ class Resource(models.Resource, models.MonitorableResource):
     inventory = models.Field(type=types.Related('inventory'))
     source = models.Field(
         default=None, help_text='The type of inventory source in use.',
-        type=click.Choice(['', 'file', 'scm', 'ec2', 'vmware', 'gce', 'azure', 'azure_rm', 'openstack',
-                           'satellite6', 'cloudforms', 'custom']),
+        type=click.Choice(INVENTORY_SOURCE_CHOICES),
     )
     credential = models.Field(type=types.Related('credential'), required=False, display=False)
     source_vars = models.Field(required=False, display=False)
@@ -104,10 +104,10 @@ class Resource(models.Resource, models.MonitorableResource):
         # 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:
-            inventory_update_id = r.json()['inventory_update']
             if monitor:
                 result = self.monitor(inventory_update_id, parent_pk=inventory_source, timeout=timeout)
             elif wait:
@@ -117,7 +117,10 @@ class Resource(models.Resource, models.MonitorableResource):
             return result
 
         # Done.
-        return {'status': 'ok'}
+        return {
+            'id': inventory_update_id,
+            'status': 'ok'
+        }
 
     @resources.command
     @click.option('--detail', is_flag=True, default=False,
diff --git a/tower_cli/resources/inventory_update.py b/tower_cli/resources/inventory_update.py
new file mode 100644
index 0000000..b66f067
--- /dev/null
+++ b/tower_cli/resources/inventory_update.py
@@ -0,0 +1,46 @@
+# Copyright 2017, Red Hat, Inc.
+#
+# 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
+
+import click
+
+from tower_cli import models
+from tower_cli.constants import LAUNCH_TYPE_CHOICES, STATUS_CHOICES, INVENTORY_SOURCE_CHOICES
+from tower_cli.cli import types
+
+
+class Resource(models.ExeResource):
+    """A resource for inventory source updates.
+    """
+    cli_help = 'Launch or monitor inventory source updates.'
+    endpoint = '/inventory_updates/'
+
+    inventory_source = models.Field(
+        key='-I',
+        type=types.Related('inventory_source'), required=True, display=True
+    )
+    name = models.Field(required=False, display=True, read_only=True)
+    launch_type = models.Field(
+        type=click.Choice(LAUNCH_TYPE_CHOICES), read_only=True, display=True
+    )
+    status = models.Field(
+        type=click.Choice(STATUS_CHOICES), read_only=True
+    )
+    job_explanation = models.Field(required=False, display=False, read_only=True)
+    created = models.Field(required=False, display=True, read_only=True)
+    elapsed = models.Field(required=False, display=True, read_only=True)
+    source = models.Field(
+        type=click.Choice(INVENTORY_SOURCE_CHOICES), display=True
+    )
diff --git a/tower_cli/resources/project.py b/tower_cli/resources/project.py
index d343120..3b2cdb9 100644
--- a/tower_cli/resources/project.py
+++ b/tower_cli/resources/project.py
@@ -248,17 +248,18 @@ class Resource(models.Resource, models.MonitorableResource):
         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:
-            project_update_id = result.json()['project_update']
             return self.monitor(project_update_id, parent_pk=pk,
                                 timeout=timeout)
         elif wait:
-            project_update_id = result.json()['project_update']
             return self.wait(project_update_id, parent_pk=pk, timeout=timeout)
 
         # Return the project update ID.
         return {
+            'id': project_update_id,
             'changed': True,
         }
 
diff --git a/tower_cli/resources/project_update.py b/tower_cli/resources/project_update.py
new file mode 100644
index 0000000..bd644ca
--- /dev/null
+++ b/tower_cli/resources/project_update.py
@@ -0,0 +1,55 @@
+# Copyright 2017, Red Hat, Inc.
+#
+# 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
+
+import click
+
+from tower_cli import models
+from tower_cli.constants import LAUNCH_TYPE_CHOICES, STATUS_CHOICES
+from tower_cli.cli import types
+
+
+class Resource(models.ExeResource):
+    """A resource for project updates.
+    """
+    cli_help = 'Launch or monitor project updates.'
+    endpoint = '/project_updates/'
+
+    project = models.Field(
+        key='-P',
+        type=types.Related('project'), required=True, display=True
+    )
+    name = models.Field(required=False, display=True, read_only=True)
+    launch_type = models.Field(
+        type=click.Choice(LAUNCH_TYPE_CHOICES), read_only=True, display=False
+    )
+    status = models.Field(
+        type=click.Choice(STATUS_CHOICES), read_only=True
+    )
+    job_type = models.Field(
+        type=click.Choice(['run', 'check']), read_only=True
+    )
+    job_explanation = models.Field(required=False, display=False, read_only=True)
+    created = models.Field(required=False, display=True, read_only=True)
+    elapsed = models.Field(required=False, display=True, read_only=True)
+    scm_type = models.Field(
+        type=types.MappedChoice([
+            ('', 'manual'),
+            ('git', 'git'),
+            ('hg', 'hg'),
+            ('svn', 'svn'),
+            ('insights', 'insights'),
+        ]), display=False
+    )


Test patch:
diff --git a/tests/test_resources_inventory_source.py b/tests/test_resources_inventory_source.py
index 23f34d0..945cc75 100644
--- a/tests/test_resources_inventory_source.py
+++ b/tests/test_resources_inventory_source.py
@@ -46,7 +46,7 @@ class UpdateTests(unittest.TestCase):
             t.register_json('/inventory_sources/1/update/',
                             {'can_update': True}, method='GET')
             t.register_json('/inventory_sources/1/update/',
-                            {}, method='POST')
+                            {'inventory_update': 42}, method='POST')
             answer = self.isr.update(1)
             self.assertEqual(answer['status'], 'ok')
 
diff --git a/tests/test_resources_project.py b/tests/test_resources_project.py
index 92a284f..21ce99e 100644
--- a/tests/test_resources_project.py
+++ b/tests/test_resources_project.py
@@ -172,7 +172,7 @@ class UpdateTests(unittest.TestCase):
             t.register_json('/projects/1/update/', {'project_update': 42},
                             method='POST')
             result = self.res.update(1)
-            self.assertEqual(result, {'changed': True})
+            self.assertEqual(result, {'changed': True, 'id': 42})
 
     def test_basic_update_with_monitor(self):
         """Establish that we are able to create a project update