Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lookup plugin for the OpenShift Container Platform #31525

Merged
merged 1 commit into from Nov 7, 2017

Conversation

kevensen
Copy link
Contributor

@kevensen kevensen commented Oct 10, 2017

SUMMARY

The OC plugin allows for the creation, deletion and modification of OpenShift resources in a cluster. There are use cases where one may wish to simply read resources from a cluster. This functionality could be built into the existing OC module. But, given the paradigm for Ansible, it seems more appropriate to have this read functionality in a lookup plugin.

ISSUE TYPE
  • New Module Pull Request
COMPONENT NAME

plugins/lookup/openshift.py

ANSIBLE VERSION
ansible 2.5.0 (oc-lookup 00299a63b0) last updated 2017/10/10 08:50:47 (GMT -400)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/Users/k6n/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/k6n/git/ansible/lib/ansible
  executable location = ./bin/ansible
  python version = 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 12:39:47) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]
ADDITIONAL INFORMATION

The following is an example of how this could be used.

---
# file: migrate.yml
- hosts: all
  vars:
    ansible_become: false
    validate_certs: false
    project_name: myproject
  tasks:
  - name: Get Project {{ project_name }}
    set_fact:
      my_project:  "{{ lookup('oc',
                                 'Project',
                                 host=inventory_host,
                                 token=hostvars[inventory_host]['ansible_sa_token'],
                                 resource_name=project_name,
                                 validate_certs=validate_certs) }}"
  - debug:
      var: my_project

@ansibot ansibot added affects_2.5 This issue/PR affects Ansible v2.5 needs_triage Needs a first human triage before being processed. new_plugin This PR includes a new plugin. plugins/lookup support:core This issue/PR relates to code supported by the Ansible Engineering Team. labels Oct 10, 2017
@bcoca bcoca removed the needs_triage Needs a first human triage before being processed. label Oct 10, 2017
@bcoca
Copy link
Member

bcoca commented Oct 10, 2017

cc @Shrews @emonty @j2sol

@bcoca
Copy link
Member

bcoca commented Oct 10, 2017

@kevensen FYI, lookup plugins now support embedded docs, see any of the existing ones for examples

@ansibot ansibot added the needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. label Oct 10, 2017
@kevensen
Copy link
Contributor Author

@bcoca Understood. I will take a look and add some documentation.

@ansibot
Copy link
Contributor

ansibot commented Oct 10, 2017

The test ansible-test sanity --test pep8 [?] failed with the following error:

lib/ansible/plugins/lookup/oc.py:47:21: W291 trailing whitespace

click here for bot help

@emonty
Copy link
Contributor

emonty commented Oct 10, 2017

@bcoca this seems fine to me- but @Shrews and I are likely the wrong people to review OpenShift things

@bcoca
Copy link
Member

bcoca commented Oct 10, 2017

@emonty sorry, i confuse my 'opens' .... stack!! not shift ...

url += self.port
url += "/"
url += api
url += "/v1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better to do this as url = "https://{0}:{1}/{2}/v1".format(self.host, self.port, api)

firstParam = True
self.kind = kind

url = self.kinds[self.kind]['baseurl']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In building up url in this function, it would be more pythonic to add each portion to a list and then use join() at the end to turn the list into a string. Something like this:

url = [self.kinds[self.kind]['baseurl']]
        if self.kinds[self.kind]['namespaced'] is True:
            url.append('/namespaces/')
            if namespace is None:
                raise AnsibleError('Kind %s requires a namespace.  \
                                    None provided' % self.kind)
            url.append(namespace)
[...]
    return "".join(url)

"and message %s against url %s" %
str(response.code, response.read(), self.url()))

return json.loads(response.read())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work on python3? I can't remember for certain but you may need to convert from bytes type to text type:

return json.loads(to_text(response.read(), errors='surrogate_or_strict'))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the docs, it looks like json.loads still accepts a string in Python3.

if response.code >= 300:
raise AnsibleError("OC Query raised exception with code %s" +
"and message %s against url %s" %
str(response.code, response.read(), self.url()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this str is in the wrong place (and the string addition is unneeded there might be a bytes type that you want to convert on python3). You probably need something more like:

raise AnsibleError("OC Query raised exception with code %s"
                   "and message %s against url %s" %
                   (response.code, to_native(response.read()), to_native(self.url()))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think self.url() should be url

from ansible.module_utils.six.moves.urllib.parse import urlencode


class OC(object):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you name this class a little more descriptively?

__metaclass__ = type

DOCUMENTATION = """
lookup: ocp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documented name does not match lookup, in any case oc/ocp seem too short, something longer and more descriptive might help users find this plugin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcoca Does ocp_query make sense? openshift_query? I'm open to suggestions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming is hard ... i think openshift should be good enough and descriptive enough as a lookup

version_added: "2.5"
short_description: Returns the JSON definition of an object in OpenShift
description:
- The Ansible OC module performs Create, Update, and Delete operations for
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a module ... also lookups should not create/update/delete anything, just query

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I was pointing out that the OC Ansible module does the Create, Update and Delete. This lookup does the Read for CRUD. If this is misleading or unnecessary I'll remove it.

bearer = "Bearer " + self.token
self.headers = {"Authorization": bearer}

def build_facts(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this isn't a part of (could be called from) __init__()? It seems to only be used right after the class is instantiated.


def url(self, kind=None, namespace=None, resource_name=None, pretty=False, labelSelector=None, fieldSelector=None, resourceVersion=None):
firstParam = True
self.kind = kind
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't appear to need to be an attribute as it's only referenced inside of this method. So you can just use kind without assigning it to self.kind.

if response.code >= 300:
raise AnsibleError("OC Query raised exception with code %s" +
"and message %s against url %s" %
str(response.code, response.read(), self.url()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same notes here as for line 215.

@ansibot
Copy link
Contributor

ansibot commented Oct 11, 2017

The test ansible-test sanity --test pep8 [?] failed with the following errors:

lib/ansible/plugins/lookup/ocp_query.py:134:13: E303 too many blank lines (2)
lib/ansible/plugins/lookup/ocp_query.py:204:9: E303 too many blank lines (2)

click here for bot help

@kevensen
Copy link
Contributor Author

@bcoca @abadger Thanks for the code reviews. Aside from bizarrely failing some integration tests, I think this is good to go. Please let me know if you have any further recommendations.

@abadger
Copy link
Contributor

abadger commented Oct 11, 2017

I think you still need to fix json.loads() If you're new to python2 vs python3 programming you might have missed that "str" itself changed between python2 and python3. I believe that response.read() is returning a byte string (str type in python2, bytes type in python3) since it is a response from the network. (That's the part that I could be wrong about though.)

body = e.read()
except AttributeError:
body = ''
raise AnsibleError("OC Query raised exception with code {0} and message {1} against url {2}".format(e.code, body, url))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need to use to_native() (from ansible.modoule_utils._text) to make sure that body is a native string type on both python2 and python3.

body = ''
raise AnsibleError("OC Query raised exception with code {0} and message {1} against url {2}".format(e.code, body, url))

for resource in json.loads(response.read())['resources']:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should use to_text() (also from ansible.module_utils._text) around response.read() here. Pretty sure that returns a byte string on both python2 and python3.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is setting data, you also want to use an error handler like this:

from ansible.module_utils._text import to_text
[...]
for resource in json.loads(to_text(response.read(), errors='surrogate_or_strict'))['resources']:

The default error handler is replace which is good for things displayed to the user but not good for preserving data.

url.append('/namespaces/')
if namespace is None:
raise AnsibleError('Kind %s requires a namespace. \
None provided' % kind)
Copy link
Contributor

@abadger abadger Oct 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. Do it like this:

                raise AnsibleError('Kind %s requires a namespace.'
                                    ' None provided' % kind)

body = ''
raise AnsibleError("OC Query raised exception with code {0} and message {1} against url {2}".format(e.code, body, url))

return json.loads(response.read())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think you need to transform to text here as well.

search_response['item_list'] = search_response.pop('items')

values = []
values.append(search_response)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could just do this as:

values = [search_response]

@abadger
Copy link
Contributor

abadger commented Oct 11, 2017

We're diagnosing the integration test failures now. We think it's that a new cryptography was just released which does not work with the pip installed on ubuntu-16.04.

@kevensen
Copy link
Contributor Author

@abadger RE: integration tests, understood. I am new to Python2 vs. Python3. I've incorporated your changes and have tested them. I am seeing consistent, expected output so I'm comfortable using the to_native and to_text.

Thanks for additional review.

@ansibot
Copy link
Contributor

ansibot commented Oct 12, 2017

The test ansible-test sanity --test pep8 [?] failed with the following error:

lib/ansible/plugins/lookup/openshift.py:154:37: E127 continuation line over-indented for visual indent

click here for bot help

@ansibot ansibot added the ci_verified Changes made in this PR are causing tests to fail. label Oct 12, 2017
@ansibot ansibot removed the ci_verified Changes made in this PR are causing tests to fail. label Oct 12, 2017
@kevensen
Copy link
Contributor Author

@bcoca @abadger This now builds successfully. When you get a chance, do you mind taking another look?

@ansibot ansibot added stale_ci This PR has been tested by CI more than one week ago. Close and re-open this PR to get it retested. stale_review Updates were made after the last review and the last review is more than 7 days old. labels Oct 20, 2017
@lucastheisen
Copy link

Im already using this plugin, and would love to see it get merged.

version_added: "2.5"
short_description: Returns the JSON definition of an object in OpenShift
description:
- This lookup plugin provides the ability to query an OpenShift Container \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description is a list, you can do list items, no need for \ continuations, im not even sure they'll display correctly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcoca Understood. Think I've run into that problem before.

a valid user or service account token.
options:
_terms:
description:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not proficient in openshift nomeclature, but it seems that resource_name makes more sense as a list an object_type as a parameter? i.e i want to get data for project1 and project2 which are Project type ... this seems to be reversed to me. (_terms vs resource_name)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe you just don't accept a 'list of terms' but only named parameters, specially cause you only take 'the first term' in your code below vs looping over them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcoca Your second suggestion is most appropriate. I didn't know enough about "_terms" and used it probably inappropriately. I've removed the terms and name the parameters consistent with the OC plugin.

@ansibot ansibot removed the stale_review Updates were made after the last review and the last review is more than 7 days old. label Nov 3, 2017
@ansibot ansibot removed the stale_ci This PR has been tested by CI more than one week ago. Close and re-open this PR to get it retested. label Nov 4, 2017
@kevensen
Copy link
Contributor Author

kevensen commented Nov 4, 2017

@lucastheisen Just FYI, I've made some minor updates per bcoca. I've tested this and it works, but if you have time to give it another look, it would be appreciated.

Specifically, the "resource kind" is a named parameter: kind. I've updated the module docs accordingly.

@ansibot ansibot removed the needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. label Nov 6, 2017
@kevensen
Copy link
Contributor Author

kevensen commented Nov 6, 2017

@abadger I think this is pretty well polished. Did you have any remaining thoughts or comments you’d like me to address?

@lucastheisen
Copy link

lucastheisen commented Nov 7, 2017

@kevensen

Just FYI, I've made some minor updates per bcoca. I've tested this and it works, but if you have time to give it another look, it would be appreciated

It appears I am already using the most recent version from your repo:

$ git log
commit 4c3b2863f63ab504ec76c867abe6feccee6eb6bd (HEAD -> master, tag: v0.2, origin/master, origin/HEAD)
Author: Kenneth D. Evensen <kevensen@redhat.com>
Date:   Tue Sep 5 15:50:15 2017 -0400

    changing variable names.  Added discover state

So, unless i am missing something here... werks fer me...

@bcoca bcoca merged commit 4eccb44 into ansible:devel Nov 7, 2017
@ansible ansible locked and limited conversation to collaborators Apr 26, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
affects_2.5 This issue/PR affects Ansible v2.5 new_plugin This PR includes a new plugin. plugins/lookup support:core This issue/PR relates to code supported by the Ansible Engineering Team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants