Repository: ansible/tower-cli
Pull request #280 (2017-03-23 14:30:38)

implement copying of resources as in the UI

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

Coverage decreased (-0.3%) to 90.739% when pulling **4806716595f047809aa8e01996c9e470fe6981f6 on evgeni:copy-resource** into **499f74ffe092d27844288f6098cdd289c0c72afd on ansible:master**.

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

Coverage decreased (-0.5%) to 90.643% when pulling **255ac93ae82efdb1e2703584fee597f5a33d3125 on evgeni:copy-resource** into **e35de20e633150a71a6fba24d819654c6b414f67 on ansible:master**.


Patch:
diff --git a/tower_cli/models/base.py b/tower_cli/models/base.py
index 6602497..afd1655 100644
--- a/tower_cli/models/base.py
+++ b/tower_cli/models/base.py
@@ -1190,33 +1190,6 @@ class ExeResource(MonitorableResource):
         # Return a success.
         return {'status': 'canceled', 'changed': changed}
 
-    @resources.command
-    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
-
 
 class Resource(ResourceMethods):
     """This is the parent class for all standard resources."""
@@ -1241,6 +1214,26 @@ class Resource(ResourceMethods):
         """
         return self.write(create_on_missing=True, **kwargs)
 
+    @resources.command(ignore_defaults=True)
+    def copy(self, pk=None, **kwargs):
+        """Copy an object.
+
+        Only the ID is used for the lookup. All provided fields are used
+        to override the old data from the copied resource.
+        """
+        orig = self.read(pk, fail_on_no_results=True,
+                         fail_on_multiple_results=True)
+        orig = orig['results'][0]
+        # Remove default values (anything where the value is None).
+        self._pop_none(kwargs)
+
+        newresource = copy(orig)
+        newresource.pop('id')
+        basename = newresource['name'].split('@', 1)[0].strip()
+        newresource['name'] = "%s @ %s" % (basename, time.strftime('%X'))
+        newresource.update(kwargs)
+        return self.write(create_on_missing=True, **newresource)
+
     @resources.command(ignore_defaults=True)
     @click.option('--create-on-missing', default=False,
                   show_default=True, type=bool, is_flag=True,


Test patch:
diff --git a/tests/test_models_base.py b/tests/test_models_base.py
index 00dd6b1..883142a 100644
--- a/tests/test_models_base.py
+++ b/tests/test_models_base.py
@@ -77,7 +77,8 @@ class ResourceMetaTests(unittest.TestCase):
 
         # Establish it has the commands we expect.
         self.assertEqual(set(MyResource.commands),
-                         set(['create', 'modify', 'list', 'get', 'delete']))
+                         set(['create', 'copy', 'modify', 'list', 'get',
+                              'delete']))
 
     def test_subclassed_commands(self):
         """Establish that commands overridden in subclasses retain their
diff --git a/tests/test_resources_job.py b/tests/test_resources_job.py
index 6d1d0c7..8891003 100644
--- a/tests/test_resources_job.py
+++ b/tests/test_resources_job.py
@@ -667,22 +667,3 @@ class CancelTests(unittest.TestCase):
             t.register('/jobs/42/cancel/', '', method='POST', status_code=405)
             with self.assertRaises(exc.TowerCLIError):
                 self.res.cancel(42, fail_if_not_running=True)
-
-
-class RelaunchTests(unittest.TestCase):
-    """A set of tasks to establish that the job relaunch command works in the
-    way that we expect.
-    """
-    def setUp(self):
-        self.res = tower_cli.get_resource('job')
-
-    def test_standard_relaunch(self):
-        """Establish that a standard relaunch command works in the way
-        we expect.
-        """
-        with client.test_mode as t:
-            data = {'id': 43}
-            t.register_json('/jobs/42/relaunch/', data, method='POST')
-            result = self.res.relaunch(42)
-            self.assertTrue(t.requests[0].url.endswith('/jobs/42/relaunch/'))
-            self.assertTrue(result['changed'])