{"action":"created","comment":{"url":"https://api.github.com/repos/ansible/tower-cli/pulls/comments/41543621","id":41543621,"diff_hunk":"@@ -914,5 +919,99 @@ def monitor(self, pk, min_interval=1, max_interval=30,\n                 secho('\\r' + ' ' * longest_string, file=outfile, nl=False)\n                 secho('\\r', file=outfile, nl=False)\n \n-        # Done; return the result\n-        return result\n+        # Return the job ID and other response data\n+        answer = OrderedDict((\n+            ('changed', True),\n+            ('id', pk),\n+        ))\n+        answer.update(result)\n+        # Make sure to return ID of resource and not update number\n+        # relevant for project creation and update\n+        answer['id'] = pk\n+        return answer\n+\n+\n+class ExeResource(MonitorableResource):\n+    \"\"\"Executable resource - defines status and cancel methods\"\"\"\n+    abstract = True\n+\n+    @resources.command\n+    @click.option('--detail', is_flag=True, default=False,\n+                  help='Print more detail.')\n+    def status(self, pk=None, detail=False, **kwargs):\n+        \"\"\"Print the current job status. This is used to check a running job.\n+        You can look up the job with the same parameters used for a get\n+        request.\"\"\"\n+        # Remove default values (anything where the value is None).\n+        self._pop_none(kwargs)\n+\n+        # Search for the record if pk not given\n+        if not pk:\n+            job = self.get(include_debug_header=True, **kwargs)\n+        # Get the job from Ansible Tower if pk given\n+        else:\n+            debug.log('Asking for job status.', header='details')\n+            finished_endpoint = '%s%d/' % (self.endpoint, pk)\n+            job = client.get(finished_endpoint).json()\n+\n+        # In most cases, we probably only want to know the status of the job\n+        # and the amount of time elapsed. However, if we were asked for\n+        # verbose information, provide it.\n+        if detail:\n+            return job\n+\n+        # Print just the information we need.\n+        return adict({\n+            'elapsed': job['elapsed'],\n+            'failed': job['failed'],\n+            'status': job['status'],\n+        })\n+\n+    @resources.command\n+    @click.option('--fail-if-not-running', is_flag=True, default=False,\n+                  help='Fail loudly if the job is not currently running.')\n+    def cancel(self, pk=None, fail_if_not_running=False, **kwargs):\n+        \"\"\"Cancel a currently running job.\n+\n+        Fails with a non-zero exit status if the job cannot be canceled.\n+        You must provide either a pk or parameters in the job's identity.\n+        \"\"\"\n+        # Search for the record if pk not given\n+        if not pk:\n+            existing_data = self.get(**kwargs)\n+            pk = existing_data['id']\n+\n+        cancel_endpoint = '%s%d/cancel/' % (self.endpoint, pk)\n+        # Attempt to cancel the job.\n+        try:\n+            client.post(cancel_endpoint)\n+            changed = True\n+        except exc.MethodNotAllowed:\n+            changed = False\n+            if fail_if_not_running:\n+                raise exc.TowerCLIError('Job not running.')\n+\n+        # Return a success.\n+        return adict({'status': 'canceled', 'changed': changed})\n+\n+\n+class Resource(ResourceMethods):\n+    \"\"\"This is the parent class for all 'standard' resources.\"\"\"\n+    abstract = True\n+\n+    @resources.command\n+    @click.option('--fail-on-found', default=False,\n+                  show_default=True, type=bool, is_flag=True,\n+                  help='If used, return an error if a matching record already '\n+                       'exists.')\n+    @click.option('--force-on-exists', default=False,\n+                  show_default=True, type=bool, is_flag=True,\n+                  help='If used, if a match is found on unique fields, other '\n+                       'fields will be updated to the provided values. If '\n+                       'False, a match causes the request to be a no-op.')\n+    def create(self, fail_on_found=False, force_on_exists=False, **kwargs):","path":"lib/tower_cli/models/base.py","position":229,"original_position":229,"commit_id":"2d9106ed400b71fc7fdb8c0efa3c3635828e319e","original_commit_id":"2d9106ed400b71fc7fdb8c0efa3c3635828e319e","user":{"login":"AlanCoding","id":1385596,"avatar_url":"https://avatars.githubusercontent.com/u/1385596?v=3","gravatar_id":"","url":"https://api.github.com/users/AlanCoding","html_url":"https://github.com/AlanCoding","followers_url":"https://api.github.com/users/AlanCoding/followers","following_url":"https://api.github.com/users/AlanCoding/following{/other_user}","gists_url":"https://api.github.com/users/AlanCoding/gists{/gist_id}","starred_url":"https://api.github.com/users/AlanCoding/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlanCoding/subscriptions","organizations_url":"https://api.github.com/users/AlanCoding/orgs","repos_url":"https://api.github.com/users/AlanCoding/repos","events_url":"https://api.github.com/users/AlanCoding/events{/privacy}","received_events_url":"https://api.github.com/users/AlanCoding/received_events","type":"User","site_admin":false},"body":"Found it - this is the part that caused the coverage decline. doc strings on commands are more than just simple suggestions. They are used in the help text on the command line, so the descriptive text should have been moved here. The parsing function was intended to operate on a specific type of string in this help text (which apparently doesn't exist in other, similar, methods), and it doesn't exist because of inadvertent over-riding.","created_at":"2015-10-08T17:37:56Z","updated_at":"2015-10-08T17:37:56Z","html_url":"https://github.com/ansible/tower-cli/pull/76#discussion_r41543621","pull_request_url":"https://api.github.com/repos/ansible/tower-cli/pulls/76","_links":{"self":{"href":"https://api.github.com/repos/ansible/tower-cli/pulls/comments/41543621"},"html":{"href":"https://github.com/ansible/tower-cli/pull/76#discussion_r41543621"},"pull_request":{"href":"https://api.github.com/repos/ansible/tower-cli/pulls/76"}}},"pull_request":{"url":"https://api.github.com/repos/ansible/tower-cli/pulls/76","id":47166459,"html_url":"https://github.com/ansible/tower-cli/pull/76","diff_url":"https://github.com/ansible/tower-cli/pull/76.diff","patch_url":"https://github.com/ansible/tower-cli/pull/76.patch","issue_url":"https://api.github.com/repos/ansible/tower-cli/issues/76","number":76,"state":"open","locked":false,"title":"ad hoc command added to resolve #61, column edit to resolve #73","user":{"login":"AlanCoding","id":1385596,"avatar_url":"https://avatars.githubusercontent.com/u/1385596?v=3","gravatar_id":"","url":"https://api.github.com/users/AlanCoding","html_url":"https://github.com/AlanCoding","followers_url":"https://api.github.com/users/AlanCoding/followers","following_url":"https://api.github.com/users/AlanCoding/following{/other_user}","gists_url":"https://api.github.com/users/AlanCoding/gists{/gist_id}","starred_url":"https://api.github.com/users/AlanCoding/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlanCoding/subscriptions","organizations_url":"https://api.github.com/users/AlanCoding/orgs","repos_url":"https://api.github.com/users/AlanCoding/repos","events_url":"https://api.github.com/users/AlanCoding/events{/privacy}","received_events_url":"https://api.github.com/users/AlanCoding/received_events","type":"User","site_admin":false},"body":"Ad hoc command added and some changes were correspondingly made to job, as these are both implemented through the ExeResource class (an extension of MonitorableResource), covering status and cancel. The previous class Resource in base.py was renamed ResouceMethods but retains the existing ordering of methods in the master branch, and this was changed from the last version of this PR. The \"create\" method is converted into a command inside of the Resource class in base.py so that the majority of resources can continue to inherit from it in the same way as before, but still leaving off the method from the command-line options for the cases of job and ad hoc resources.\r\n\r\nSeveral changes were also made in order to print a free-form list of columns when running, monitoring, or checking the status of an ad hoc command, but these changes also apply to running a job. These methods were excluded from the special case in the _format_human method that checked if a unique combination of keys existed in the returned dictionary, and narrowed the list of columns if that were the case. Almost every other resource+command combination (which one set of exceptions, I can elaborate on later) has the fields flagged with display=True printed. Since ad hoc commands borrow fields (which are typically displayed in this way) from job template, it was written to follow this format, even though doing so demanded making some changes to the way status, cancel, and monitor operate. That is what is typically going on when you see **kwargs addition in the code, and I have attempted to be as meticulous as possible checking to see that these alterations don't negatively affect anything else.\r\n\r\nAdditionally, some errors with lookups were changed in a way that affected more resources than just ad hoc. For an example of what I'm talking about, I would like to be able to call `job launch --job-template=hello_world` as well as `job launch --job-template=7`, if that specific job template has a pk of 7. Switching logic for integers versus names should be pretty automatic, and it typically is, but some cases where the modify method is over-ridden (for instance), and when multiple contributors made changes to these implementations, support for one or the other has been unintentionally dropped. Some edits here deal with that, either with the pk specifier, or a custom set of logic that has already been used in a number of resources here and there. A more thorough audit of this behavior is left for the future.\r\n\r\nOne problem with test coverage did come up for this PR in particular. There is a statement that replaces all occurrences of underscores with hyphens in the help_text. I don't actually see where underscores were ever used in the help text, so this explains why it was not covered. It's harder to explain why it was covered in the past, but I will look into it.","created_at":"2015-10-08T15:18:04Z","updated_at":"2015-10-08T17:37:56Z","closed_at":null,"merged_at":null,"merge_commit_sha":"0f1910fd547855a2c9f0b284659584c5c1c6bc9f","assignee":null,"milestone":{"url":"https://api.github.com/repos/ansible/tower-cli/milestones/1","html_url":"https://github.com/ansible/tower-cli/milestones/release_2.3.0","labels_url":"https://api.github.com/repos/ansible/tower-cli/milestones/1/labels","id":1326740,"number":1,"title":"release_2.3.0","description":"","creator":{"login":"jlaska","id":1051173,"avatar_url":"https://avatars.githubusercontent.com/u/1051173?v=3","gravatar_id":"","url":"https://api.github.com/users/jlaska","html_url":"https://github.com/jlaska","followers_url":"https://api.github.com/users/jlaska/followers","following_url":"https://api.github.com/users/jlaska/following{/other_user}","gists_url":"https://api.github.com/users/jlaska/gists{/gist_id}","starred_url":"https://api.github.com/users/jlaska/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jlaska/subscriptions","organizations_url":"https://api.github.com/users/jlaska/orgs","repos_url":"https://api.github.com/users/jlaska/repos","events_url":"https://api.github.com/users/jlaska/events{/privacy}","received_events_url":"https://api.github.com/users/jlaska/received_events","type":"User","site_admin":false},"open_issues":6,"closed_issues":8,"state":"open","created_at":"2015-09-28T13:07:27Z","updated_at":"2015-10-08T15:38:29Z","due_on":"2015-10-09T04:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/ansible/tower-cli/pulls/76/commits","review_comments_url":"https://api.github.com/repos/ansible/tower-cli/pulls/76/comments","review_comment_url":"https://api.github.com/repos/ansible/tower-cli/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ansible/tower-cli/issues/76/comments","statuses_url":"https://api.github.com/repos/ansible/tower-cli/statuses/2d9106ed400b71fc7fdb8c0efa3c3635828e319e","head":{"label":"AlanCoding:ad_hoc_red","ref":"ad_hoc_red","sha":"2d9106ed400b71fc7fdb8c0efa3c3635828e319e","user":{"login":"AlanCoding","id":1385596,"avatar_url":"https://avatars.githubusercontent.com/u/1385596?v=3","gravatar_id":"","url":"https://api.github.com/users/AlanCoding","html_url":"https://github.com/AlanCoding","followers_url":"https://api.github.com/users/AlanCoding/followers","following_url":"https://api.github.com/users/AlanCoding/following{/other_user}","gists_url":"https://api.github.com/users/AlanCoding/gists{/gist_id}","starred_url":"https://api.github.com/users/AlanCoding/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlanCoding/subscriptions","organizations_url":"https://api.github.com/users/AlanCoding/orgs","repos_url":"https://api.github.com/users/AlanCoding/repos","events_url":"https://api.github.com/users/AlanCoding/events{/privacy}","received_events_url":"https://api.github.com/users/AlanCoding/received_events","type":"User","site_admin":false},"repo":{"id":40971041,"name":"tower-cli","full_name":"AlanCoding/tower-cli","owner":{"login":"AlanCoding","id":1385596,"avatar_url":"https://avatars.githubusercontent.com/u/1385596?v=3","gravatar_id":"","url":"https://api.github.com/users/AlanCoding","html_url":"https://github.com/AlanCoding","followers_url":"https://api.github.com/users/AlanCoding/followers","following_url":"https://api.github.com/users/AlanCoding/following{/other_user}","gists_url":"https://api.github.com/users/AlanCoding/gists{/gist_id}","starred_url":"https://api.github.com/users/AlanCoding/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlanCoding/subscriptions","organizations_url":"https://api.github.com/users/AlanCoding/orgs","repos_url":"https://api.github.com/users/AlanCoding/repos","events_url":"https://api.github.com/users/AlanCoding/events{/privacy}","received_events_url":"https://api.github.com/users/AlanCoding/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/AlanCoding/tower-cli","description":"Command line tool and client library for Ansible Tower's REST API","fork":true,"url":"https://api.github.com/repos/AlanCoding/tower-cli","forks_url":"https://api.github.com/repos/AlanCoding/tower-cli/forks","keys_url":"https://api.github.com/repos/AlanCoding/tower-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/AlanCoding/tower-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/AlanCoding/tower-cli/teams","hooks_url":"https://api.github.com/repos/AlanCoding/tower-cli/hooks","issue_events_url":"https://api.github.com/repos/AlanCoding/tower-cli/issues/events{/number}","events_url":"https://api.github.com/repos/AlanCoding/tower-cli/events","assignees_url":"https://api.github.com/repos/AlanCoding/tower-cli/assignees{/user}","branches_url":"https://api.github.com/repos/AlanCoding/tower-cli/branches{/branch}","tags_url":"https://api.github.com/repos/AlanCoding/tower-cli/tags","blobs_url":"https://api.github.com/repos/AlanCoding/tower-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/AlanCoding/tower-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/AlanCoding/tower-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/AlanCoding/tower-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/AlanCoding/tower-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/AlanCoding/tower-cli/languages","stargazers_url":"https://api.github.com/repos/AlanCoding/tower-cli/stargazers","contributors_url":"https://api.github.com/repos/AlanCoding/tower-cli/contributors","subscribers_url":"https://api.github.com/repos/AlanCoding/tower-cli/subscribers","subscription_url":"https://api.github.com/repos/AlanCoding/tower-cli/subscription","commits_url":"https://api.github.com/repos/AlanCoding/tower-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/AlanCoding/tower-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/AlanCoding/tower-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/AlanCoding/tower-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/AlanCoding/tower-cli/contents/{+path}","compare_url":"https://api.github.com/repos/AlanCoding/tower-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/AlanCoding/tower-cli/merges","archive_url":"https://api.github.com/repos/AlanCoding/tower-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/AlanCoding/tower-cli/downloads","issues_url":"https://api.github.com/repos/AlanCoding/tower-cli/issues{/number}","pulls_url":"https://api.github.com/repos/AlanCoding/tower-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/AlanCoding/tower-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/AlanCoding/tower-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/AlanCoding/tower-cli/labels{/name}","releases_url":"https://api.github.com/repos/AlanCoding/tower-cli/releases{/id}","created_at":"2015-08-18T12:37:27Z","updated_at":"2015-08-18T12:37:28Z","pushed_at":"2015-10-08T14:48:58Z","git_url":"git://github.com/AlanCoding/tower-cli.git","ssh_url":"git@github.com:AlanCoding/tower-cli.git","clone_url":"https://github.com/AlanCoding/tower-cli.git","svn_url":"https://github.com/AlanCoding/tower-cli","homepage":"ansible.com/tower","size":419,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"ansible:master","ref":"master","sha":"402e919b67ceeadc725e45874b4ed8b525738fc1","user":{"login":"ansible","id":1507452,"avatar_url":"https://avatars.githubusercontent.com/u/1507452?v=3","gravatar_id":"","url":"https://api.github.com/users/ansible","html_url":"https://github.com/ansible","followers_url":"https://api.github.com/users/ansible/followers","following_url":"https://api.github.com/users/ansible/following{/other_user}","gists_url":"https://api.github.com/users/ansible/gists{/gist_id}","starred_url":"https://api.github.com/users/ansible/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ansible/subscriptions","organizations_url":"https://api.github.com/users/ansible/orgs","repos_url":"https://api.github.com/users/ansible/repos","events_url":"https://api.github.com/users/ansible/events{/privacy}","received_events_url":"https://api.github.com/users/ansible/received_events","type":"Organization","site_admin":false},"repo":{"id":12345160,"name":"tower-cli","full_name":"ansible/tower-cli","owner":{"login":"ansible","id":1507452,"avatar_url":"https://avatars.githubusercontent.com/u/1507452?v=3","gravatar_id":"","url":"https://api.github.com/users/ansible","html_url":"https://github.com/ansible","followers_url":"https://api.github.com/users/ansible/followers","following_url":"https://api.github.com/users/ansible/following{/other_user}","gists_url":"https://api.github.com/users/ansible/gists{/gist_id}","starred_url":"https://api.github.com/users/ansible/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ansible/subscriptions","organizations_url":"https://api.github.com/users/ansible/orgs","repos_url":"https://api.github.com/users/ansible/repos","events_url":"https://api.github.com/users/ansible/events{/privacy}","received_events_url":"https://api.github.com/users/ansible/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ansible/tower-cli","description":"Command line tool and client library for Ansible Tower's REST API","fork":false,"url":"https://api.github.com/repos/ansible/tower-cli","forks_url":"https://api.github.com/repos/ansible/tower-cli/forks","keys_url":"https://api.github.com/repos/ansible/tower-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ansible/tower-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ansible/tower-cli/teams","hooks_url":"https://api.github.com/repos/ansible/tower-cli/hooks","issue_events_url":"https://api.github.com/repos/ansible/tower-cli/issues/events{/number}","events_url":"https://api.github.com/repos/ansible/tower-cli/events","assignees_url":"https://api.github.com/repos/ansible/tower-cli/assignees{/user}","branches_url":"https://api.github.com/repos/ansible/tower-cli/branches{/branch}","tags_url":"https://api.github.com/repos/ansible/tower-cli/tags","blobs_url":"https://api.github.com/repos/ansible/tower-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ansible/tower-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ansible/tower-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/ansible/tower-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ansible/tower-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/ansible/tower-cli/languages","stargazers_url":"https://api.github.com/repos/ansible/tower-cli/stargazers","contributors_url":"https://api.github.com/repos/ansible/tower-cli/contributors","subscribers_url":"https://api.github.com/repos/ansible/tower-cli/subscribers","subscription_url":"https://api.github.com/repos/ansible/tower-cli/subscription","commits_url":"https://api.github.com/repos/ansible/tower-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/ansible/tower-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/ansible/tower-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/ansible/tower-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/ansible/tower-cli/contents/{+path}","compare_url":"https://api.github.com/repos/ansible/tower-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ansible/tower-cli/merges","archive_url":"https://api.github.com/repos/ansible/tower-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ansible/tower-cli/downloads","issues_url":"https://api.github.com/repos/ansible/tower-cli/issues{/number}","pulls_url":"https://api.github.com/repos/ansible/tower-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/ansible/tower-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/ansible/tower-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ansible/tower-cli/labels{/name}","releases_url":"https://api.github.com/repos/ansible/tower-cli/releases{/id}","created_at":"2013-08-24T15:19:04Z","updated_at":"2015-09-30T02:52:33Z","pushed_at":"2015-10-08T15:18:04Z","git_url":"git://github.com/ansible/tower-cli.git","ssh_url":"git@github.com:ansible/tower-cli.git","clone_url":"https://github.com/ansible/tower-cli.git","svn_url":"https://github.com/ansible/tower-cli","homepage":"ansible.com/tower","size":1070,"stargazers_count":87,"watchers_count":87,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":35,"mirror_url":null,"open_issues_count":14,"forks":35,"open_issues":14,"watchers":87,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ansible/tower-cli/pulls/76"},"html":{"href":"https://github.com/ansible/tower-cli/pull/76"},"issue":{"href":"https://api.github.com/repos/ansible/tower-cli/issues/76"},"comments":{"href":"https://api.github.com/repos/ansible/tower-cli/issues/76/comments"},"review_comments":{"href":"https://api.github.com/repos/ansible/tower-cli/pulls/76/comments"},"review_comment":{"href":"https://api.github.com/repos/ansible/tower-cli/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ansible/tower-cli/pulls/76/commits"},"statuses":{"href":"https://api.github.com/repos/ansible/tower-cli/statuses/2d9106ed400b71fc7fdb8c0efa3c3635828e319e"}}}}