Repository: ansible/tower-cli
Pull request #547 (2018-06-13 03:16:02)

Update application resource and documentation
application requires an organization field, and doesn't
use the user field at all.

Add documentation of the application resource

AlanCoding: Looks good to me, only thing that needs fixing is a minor flake8 error.

@rooftopcellist could you have a quick look, I'll be ready to merge when Travis is passing.
AlanCoding: isn't the .rst file automatically generated?

Patch:
diff --git a/docs/source/api_ref/resources/application.rst b/docs/source/api_ref/resources/application.rst
new file mode 100644
index 0000000..9e3a285
--- /dev/null
+++ b/docs/source/api_ref/resources/application.rst
@@ -0,0 +1,32 @@
+Application
+===========
+
+Description
+-----------
+
+This resource is used for managing application resources in Tower.
+
+Fields Table
+------------
+.. <table goes here>
+
++-------------------------+--------------------------------------------------------------------+------------------------------------+----------+-------+-----------+---------+
+|name                     |type                                                                |help_text                           |read_only |unique |filterable |required |
++=========================+====================================================================+====================================+==========+=======+===========+=========+
+|name                     |String                                                              |The name field.                     |False     |True   |True       |True     |
++-------------------------+--------------------------------------------------------------------+------------------------------------+----------+-------+-----------+---------+
+|client_type              |Choices: public, confidential                                       |The client_type field.              |False     |False  |True       |True     |
++-------------------------+--------------------------------------------------------------------+------------------------------------+----------+-------+-----------+---------+
+|organization             |Resource organization                                               |The organization field.             |False     |False  |True       |True     |
++-------------------------+--------------------------------------------------------------------+------------------------------------+----------+-------+-----------+---------+
+|authorization_grant_type |Choices: authorization-code, implicit, password, client-credentials |The authorization_grant_type field. |False     |False  |True       |True     |
++-------------------------+--------------------------------------------------------------------+------------------------------------+----------+-------+-----------+---------+
+|skip_authorization       |BOOL                                                                |The skip_authorization field.       |False     |False  |True       |False    |
++-------------------------+--------------------------------------------------------------------+------------------------------------+----------+-------+-----------+---------+
+
+.. <table goes here>
+
+API Specification
+-----------------
+.. autoclass:: tower_cli.resources.application.Resource
+   :members: copy, create, delete, get, list, modify
diff --git a/docs/source/cli_ref/index.rst b/docs/source/cli_ref/index.rst
index 7137ab4..d459aa1 100644
--- a/docs/source/cli_ref/index.rst
+++ b/docs/source/cli_ref/index.rst
@@ -209,8 +209,6 @@ and ``tower-cli {resource} {action} --help`` shows details of the usage of this
       -p, --tower-password TEXT       Password to use to authenticate to Ansible
                                       Tower. This will take precedence over a
                                       password provided to `tower config`, if any.
-                                      If set to ASK you will be prompted to
-                                      input the password.
       -u, --tower-username TEXT       Username to use to authenticate to Ansible
                                       Tower. This will take precedence over a
                                       username provided to `tower config`, if any.
diff --git a/tower_cli/api.py b/tower_cli/api.py
index fb287f1..3e47378 100644
--- a/tower_cli/api.py
+++ b/tower_cli/api.py
@@ -20,12 +20,6 @@ import functools
 import json
 import stat
 import warnings
-
-try:
-    from urlparse import urljoin, urlparse
-except Exception as e:
-    from urllib.parse import urljoin, urlparse
-
 from datetime import datetime as dt
 
 from requests.exceptions import ConnectionError, SSLError
@@ -193,16 +187,9 @@ class Client(Session):
                 'Can not verify ssl with non-https protocol. Change the '
                 'verify_ssl configuration setting to continue.'
             )
-        # Validate that we have either an http or https based URL
-        url_pieces = urlparse(host)
-        if url_pieces[0] not in ['http', 'https']:
-            raise exc.ConnectionError('URL must be http(s), {} is not valid'.format(url_pieces[0]))
-
-        prefix = urljoin(host, '/api/')
+        prefix = os.path.sep.join([host.rstrip('/'), 'api', ''])
         if include_version:
-            # We add the / to the end of {} so that our URL has the ending slash.
-            prefix = urljoin(prefix, "{}/".format(CUR_API_VERSION))
-
+            prefix = os.path.sep.join([prefix.rstrip('/'), CUR_API_VERSION, ''])
         return prefix
 
     @functools.wraps(Session.request)
diff --git a/tower_cli/conf.py b/tower_cli/conf.py
index bdd65e9..4552f26 100644
--- a/tower_cli/conf.py
+++ b/tower_cli/conf.py
@@ -23,7 +23,6 @@ import os
 import stat
 import warnings
 from functools import wraps
-from sys import argv
 
 import six
 from six.moves import configparser
@@ -341,8 +340,6 @@ settings = Settings()
 
 
 def _apply_runtime_setting(ctx, param, value):
-    if param.name == 'tower_password' and value == 'ASK' and '--help' not in argv:
-        value = click.prompt('Enter tower password', type=str, hide_input=True)
     settings.set_or_reset_runtime_param(param.name, value)
 
 
@@ -402,8 +399,7 @@ def with_global_options(method):
         '-p', '--tower-password',
         help='Password to use to authenticate to Ansible Tower. '
              'This will take precedence over a password provided to '
-             '`tower config`, if any. If value is ASK you will be '
-             'prompted for the password',
+             '`tower config`, if any.',
         required=False, callback=_apply_runtime_setting,
         is_eager=True
     )(method)
diff --git a/tower_cli/resources/application.py b/tower_cli/resources/application.py
index e42dbe3..246df52 100644
--- a/tower_cli/resources/application.py
+++ b/tower_cli/resources/application.py
@@ -13,19 +13,34 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import click
+
 from tower_cli import models
 from tower_cli.cli import types
 
 
+CLIENT_TYPES = [
+    'confidential',
+    'public'
+]
+
+GRANT_TYPES = [
+    "authorization-code",
+    "implicit",
+    "password",
+    "client-credentials",
+]
+
+
 class Resource(models.Resource):
     """A resource for OAuth2 applications."""
     cli_help = 'Manage OAuth2 applications.'
     endpoint = '/applications/'
-
-    user = models.Field(type=types.Related('user'), required=True)
+    dependencies = ['organization']
 
     name = models.Field(unique=True)
-    client_type = models.Field(required=True)
+    client_type = models.Field(type=click.Choice(CLIENT_TYPES), required=True)
     redirect_uris = models.Field(required=False)
-    authorization_grant_type = models.Field(required=True)
-    skip_authorization = models.Field(required=False)
+    authorization_grant_type = models.Field(type=click.Choice(GRANT_TYPES), required=True)
+    skip_authorization = models.Field(type=click.BOOL, required=False)
+    organization = models.Field(type=types.Related('organization'), required=True)
diff --git a/tower_cli/resources/project.py b/tower_cli/resources/project.py
index de1b21f..a62cf26 100644
--- a/tower_cli/resources/project.py
+++ b/tower_cli/resources/project.py
@@ -114,7 +114,7 @@ class Resource(models.Resource, models.MonitorableResource):
             # Processing the organization flag depends on version
             debug.log('Checking Organization Relationship.', header='details')
             r = client.options('/projects/')
-            if 'organization' in r.json().get('actions', {}).get('POST', {}):
+            if 'organization' in r.json()['actions']['POST']:
                 kwargs['organization'] = organization
             else:
                 post_associate = True


Test patch:
diff --git a/tests/test_api.py b/tests/test_api.py
index 45818a0..98cc629 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -46,24 +46,12 @@ class ClientTests(unittest.TestCase):
         with settings.runtime_values(host='33.33.33.33'):
             self.assertEqual(client.get_prefix(), 'https://33.33.33.33/api/%s/' % CUR_API_VERSION)
 
-    def test_prefix_https_protocol(self):
-        """Establish that the http protocol works
-        """
-        with settings.runtime_values(host='https://33.33.33.33'):
-            self.assertEqual(client.get_prefix(), 'https://33.33.33.33/api/%s/' % CUR_API_VERSION)
-
-    def test_prefix_http_protocol(self):
-        """Establish that the http protocol works
-        """
-        with settings.runtime_values(host='http://33.33.33.33', verify_ssl=False):
-            self.assertEqual(client.get_prefix(), 'http://33.33.33.33/api/%s/' % CUR_API_VERSION)
-
     def test_prefix_explicit_protocol(self):
-        """Establish that the prefix property can not start with non-http protocol.
+        """Establish that the prefix property returns the appropriate
+        URL prefix and don't clobber over an explicit protocol.
         """
         with settings.runtime_values(host='bogus://33.33.33.33/'):
-            with self.assertRaises(exc.ConnectionError):
-                client.get_prefix()
+            self.assertEqual(client.get_prefix(), 'bogus://33.33.33.33/api/%s/' % CUR_API_VERSION)
 
     def test_request_ok(self):
         """Establish that a request that returns a valid JSON response