Repository: ansible/tower-cli (python)

Issue:
[v2] Remove special handling for pre-3.0 credentials
The Credential resource has a bunch of special behavior to accommodate old version switches.

I think this is because `user` and `team` used to be genuine fields, but these columns were removed in Tower 3.0. None of this is needed for post-3.2 tower-cli

Pull request:
remove old version logic not needed in v2 API
Resolves #358

Some basic demoing:

```
$ tower-cli credential create --credential-type="Source Control" --inputs='{"username": "asdf", "password": "fdsa"}' --user=admin --name='asdf' -v
*** DETAILS: The credential_type field is given as a name; looking it up. *****
*** DETAILS: Getting the record. **********************************************
GET http://localhost:8013/api/v2/credential_types/
Params: {'name': 'Source Control'}

*** DETAILS: The user field is given as a name; looking it up. ****************
*** DETAILS: Getting the record. **********************************************
GET http://localhost:8013/api/v2/users/
Params: {'username': 'admin'}

*** DETAILS: Checking for an existing record. *********************************
GET http://localhost:8013/api/v2/credentials/
Params: {'name': u'asdf'}

*** DETAILS: Writing the record. **********************************************
POST http://localhost:8013/api/v2/credentials/
Data: {'inputs': {'username': 'asdf', 'password': 'fdsa'}, 'credential_type': 2, 'user': 1, 'name': u'asdf'}

Resource changed.
== ==== =============== 
id name credential_type 
== ==== =============== 
 8 asdf               2
== ==== =============== 
```

We might have some other bugs to file - it's not using credential type in the lookup, not providing name doesn't raise an error right away.

Patch:
diff --git a/tower_cli/models/fields.py b/tower_cli/models/fields.py
index 92aade7..97c3004 100644
--- a/tower_cli/models/fields.py
+++ b/tower_cli/models/fields.py
@@ -27,7 +27,7 @@ class Field(object):
                  display=True, filterable=True, help_text=None,
                  is_option=True, password=False, read_only=False,
                  required=True, show_default=False, unique=False,
-                 multiple=False, col_width=None):
+                 multiple=False, no_lookup=False, col_width=None):
         # Init the name to blank.
         # What's going on here: This is set by the ResourceMeta metaclass
         # when the **resource** is instantiated.
@@ -49,7 +49,7 @@ class Field(object):
         self.show_default = show_default
         self.unique = unique
         self.multiple = multiple
-        self.no_lookup = False
+        self.no_lookup = no_lookup
         self.col_width = col_width
 
         # If this is a password, display is always off.
diff --git a/tower_cli/resources/credential.py b/tower_cli/resources/credential.py
index ea87631..e631779 100644
--- a/tower_cli/resources/credential.py
+++ b/tower_cli/resources/credential.py
@@ -13,9 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from tower_cli import models, resources
-from tower_cli.utils import debug
-from tower_cli.api import client
+from tower_cli import models
 from tower_cli.cli import types
 
 
@@ -29,47 +27,9 @@ class Resource(models.Resource):
     description = models.Field(required=False, display=False)
 
     # Who owns this credential?
-    user = models.Field(display=False, type=types.Related('user'), required=False)
-    team = models.Field(display=False, type=types.Related('team'), required=False)
+    user = models.Field(display=False, type=types.Related('user'), required=False, no_lookup=True)
+    team = models.Field(display=False, type=types.Related('team'), required=False, no_lookup=True)
     organization = models.Field(display=False, type=types.Related('organization'), required=False)
 
     credential_type = models.Field(type=types.Related('credential_type'))
     inputs = models.Field(type=types.StructuredInput(), required=False, display=False)
-
-    @resources.command
-    def create(self, **kwargs):
-        """Create a credential.
-
-        Fields in the resource's `identity` tuple are used for a lookup;
-        if a match is found, then no-op (unless `force_on_exists` is set) but
-        do not fail (unless `fail_on_found` is set).
-
-        =====API DOCS=====
-        Create a credential.
-
-        :param fail_on_found: Flag that if set, the operation fails if an object matching the unique criteria
-                              already exists.
-        :type fail_on_found: bool
-        :param force_on_exists: Flag that if set, then if a match is found on unique fields, other fields will
-                                be updated to the provided values.; If unset, a match causes the request to be
-                                a no-op.
-        :type force_on_exists: bool
-        :param `**kwargs`: Keyword arguements which, all together, will be used as POST body to create the
-                           resource object.
-        :returns: A dictionary combining the JSON output of the created resource, as well as two extra fields:
-                  "changed", a flag indicating if the resource is created successfully; "id", an integer which
-                  is the primary key of the created object.
-        :rtype: dict
-
-
-        =====API DOCS=====
-        """
-        if (kwargs.get('user', False) or kwargs.get('team', False) or
-                kwargs.get('organization', False)):
-            debug.log('Checking Project API Details.', header='details')
-            r = client.options('/credentials/')
-            if 'organization' in r.json()['actions']['POST']:
-                for i in range(len(self.fields)):
-                    if self.fields[i].name in ('user', 'team'):
-                        self.fields[i].no_lookup = True
-        return super(Resource, self).create(**kwargs)
diff --git a/tower_cli/resources/job_template.py b/tower_cli/resources/job_template.py
index a2d502f..35f6ea5 100644
--- a/tower_cli/resources/job_template.py
+++ b/tower_cli/resources/job_template.py
@@ -150,46 +150,6 @@ class Resource(models.SurveyResource):
         """
         return self._disassoc('labels', job_template, label)
 
-    @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',


Test patch:
diff --git a/tests/test_resources_credential.py b/tests/test_resources_credential.py
index 73d7569..50fbb15 100644
--- a/tests/test_resources_credential.py
+++ b/tests/test_resources_credential.py
@@ -73,12 +73,11 @@ class CredentialTests(unittest.TestCase):
             self.assertTrue(cred_res.fields[3].no_lookup)
 
             # Verify request data is correct
-            self.assertEqual(len(t.requests), 3)
-            self.assertEqual(t.requests[0].method, 'OPTIONS')
-            self.assertEqual(t.requests[1].method, 'GET')
-            self.assertEqual(t.requests[2].method, 'POST')
-            self.assertTrue('name=foobar' in t.requests[1].url)
+            self.assertEqual(len(t.requests), 2)
+            self.assertEqual(t.requests[0].method, 'GET')
+            self.assertEqual(t.requests[1].method, 'POST')
+            self.assertIn('name=foobar', t.requests[0].url)
             # Make sure special fields not used for GET
-            self.assertTrue('user' not in t.requests[1].url)
+            self.assertTrue('user' not in t.requests[0].url)
             # Make sure special files are used in actual POST
-            self.assertTrue('user' in t.requests[2].body)
+            self.assertIn('user', t.requests[1].body)
diff --git a/tests/test_resources_job_template.py b/tests/test_resources_job_template.py
index dc04fd8..575cbee 100644
--- a/tests/test_resources_job_template.py
+++ b/tests/test_resources_job_template.py
@@ -157,31 +157,6 @@ class TemplateTests(unittest.TestCase):
             self.assertEqual(t.requests[1].body,
                              json.dumps({'disassociate': True, 'id': 84}))
 
-    def test_associate_credential(self):
-        """Establish that the associate method makes the HTTP requests
-        that we expect.
-        """
-        with client.test_mode as t:
-            t.register_json('/job_templates/42/extra_credentials/?id=84',
-                            {'count': 0, 'results': []})
-            t.register_json('/job_templates/42/extra_credentials/', {}, method='POST')
-            self.res.associate_credential(42, 84)
-            self.assertEqual(t.requests[1].body,
-                             json.dumps({'associate': True, 'id': 84}))
-
-    def test_disassociate_credential(self):
-        """Establish that the disassociate method makes the HTTP requests
-        that we expect.
-        """
-        with client.test_mode as t:
-            t.register_json('/job_templates/42/extra_credentials/?id=84',
-                            {'count': 1, 'results': [{'id': 84}],
-                             'next': None, 'previous': None})
-            t.register_json('/job_templates/42/extra_credentials/', {}, method='POST')
-            self.res.disassociate_credential(42, 84)
-            self.assertEqual(t.requests[1].body,
-                             json.dumps({'disassociate': True, 'id': 84}))
-
     def test_associate_notification_template(self):
         """Establish that a job template should be able to associate itself
         with an existing notification template.