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

port foreman_domain, foreman_search_facts and katello_content_credential to apypie #261

Merged
merged 4 commits into from
May 21, 2019

Conversation

evgeni
Copy link
Member

@evgeni evgeni commented Apr 2, 2019

Some time ago, I've written apypie as a Python port of apipie-bindings. This has the benefit that it can fetch the API definition from the running Foreman instance and thus does not need to "know" about the various resources. It's far from feature complete (or even bug-free), but I thought it never will become if it has no users, so here we are.

This PR ports foreman_domain to apypie instead of nailgun. While doing so, I've tried to make the ForemanAnsibleModule API more Pythonic. The code also contains a few FIXMEs, which I'm still unsure about how to handle cleanly. Also, the method/variable names are inconsistent, as I've copied a lot from the nailgun helper, but only adapted the naming half way through.

@evgeni
Copy link
Member Author

evgeni commented Apr 2, 2019

@akofink @sean797 @mdellweg @manuelbonk @Fobhep @ehelms I want y'all thoughts

@@ -87,36 +86,27 @@
RETURN = ''' # '''

try:
from ansible.module_utils.ansible_nailgun_cement import (
find_domain,
find_locations,
Copy link
Member

Choose a reason for hiding this comment

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

This, orgs and proxies do make sense to me to keep since they often show up in APIs. Perhaps there needs to a compatible apypie helper?

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about that (even had them at first), but then I thought that find_foo(…) is not that shorter than find_resource_by_name('foo', …) and having that for only a few resources is also weird?

Copy link
Member

Choose a reason for hiding this comment

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

There is still the thin=True as well. Perhaps the normalization function should be factored out and do it there.

method: GET
uri: https://foreman.example.com/katello/api/v2/ping
uri: https://192.168.121.223/api/status
Copy link
Member

Choose a reason for hiding this comment

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

I quite liked the foreman.example.com domain :)

Copy link
Member Author

Choose a reason for hiding this comment

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

blah :)
I'll fix the fixtures at some point, yeah.

module_utils/foreman_helper.py Outdated Show resolved Hide resolved
Copy link
Member

@sean797 sean797 left a comment

Choose a reason for hiding this comment

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

Super awesome @evgeni I was wondering what happened with apypie only yesterday. really nice

module_utils/foreman_helper.py Outdated Show resolved Hide resolved
module_utils/foreman_helper.py Show resolved Hide resolved
module_utils/foreman_helper.py Outdated Show resolved Hide resolved


def sanitize_entity_dict(entity_dict, name_map):
return {value: entity_dict[key] for key, value in name_map.items() if key in entity_dict}
Copy link
Member

Choose a reason for hiding this comment

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

How about:

Suggested change
return {value: entity_dict[key] for key, value in name_map.items() if key in entity_dict}
result = {}
for (key, (value, normalization)) in name_map.items():
if key in entity_dict:
if normalization:
result[value] = module.find_resources(normalization, entity_dict[key], thin=True)
else:
result[value]: entity_dict[key]
return result

Though it doesn't deal with the by name/id problem, it does allow dropping a lot of code from individual modules. Perhaps the mapping needs to be a bit more expressive though.

Copy link
Member Author

Choose a reason for hiding this comment

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

sanitize_entity_dict is something we also use in the nailgun based modules, so I'd prefer not to touch it just yet. but I agree, there is certainly room for further improvements

@evgeni
Copy link
Member Author

evgeni commented Apr 5, 2019

This now also includes an example for Katello 💃

@@ -120,9 +124,13 @@ def find_resource(self, resource, search, failsafe=False, thin=False):
result = self.show_resource(resource, result['id'])
return result

def find_resource_by_name(self, resource, name, failsafe=False, thin=False):
def find_resource_by_name(self, resource, name, organization_id=None, failsafe=False, thin=False):
Copy link
Member

Choose a reason for hiding this comment

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

We may want to allow passing in other params, for exmaple https://theforeman.org/plugins/katello/3.9/api/apidoc/v2/repository_sets/index.html requires product_id, I'm fairly sure there are others.

But I think we can change this at a later date when/if we require them.

Copy link
Member

Choose a reason for hiding this comment

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

Yea, maybe this could be done with a dict parameter or with kwargs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, this is something I should change before merging!
Otherwise fixing the API later is pain.

Copy link
Member Author

Choose a reason for hiding this comment

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

updated to be a dict.

@mdellweg
Copy link
Member

Is this something that can be extended easily to be using APIv3 (Graphql)?

@evgeni
Copy link
Member Author

evgeni commented Apr 15, 2019

Is this something that can be extended easily to be using APIv3 (Graphql)?

I don't know much about the GraphQL API, but I don't think this will be easy.

@evgeni
Copy link
Member Author

evgeni commented Apr 17, 2019

Is this something that can be extended easily to be using APIv3 (Graphql)?

I don't know much about the GraphQL API, but I don't think this will be easy.

After looking a bit closer on how GraphQL works, this will not work with our GraphQL API as such, but we should be able to create a similar groundwork for APIv3 with learnings from this and previous PRs.

@evgeni
Copy link
Member Author

evgeni commented Apr 18, 2019

I think this is now at a state where we should talk about: do we want that as a direction forward, or was this a nice PoC but we stick to nailgun.

Let me re-iterate the pros and cons for the switch:

Pros

  • apypie has only one dependency: requests, so shipping it to users (via RPM or whatever) is much easier than nailgun which pulls in multiple Python libs (which are not packaged in Fedora/EPEL).
  • We're using the API JSON as published by the server, thus always having the matching list of entities and their params. No need to update the library if a new entity is added or changed.
  • This also means we don't really need to branch per Foreman/Katello release (although Hammer does it, so there might be cases where we still have to).

Cons

  • apypie is a young library, which didn't see much adoption yet, so there will be bugs and dragons
  • we're losing typechecking of params, as these are not exposed in the API JSON and apypie has no further knowledge about them
  • we're losing relationships between entities, so the modules will need to potentially pull more data themself.

Known Bugs

  • You currently have to wipe your ~/.cache/apypie/ before recording VCR tapes, as otherwise apypie takes the API JSON from the cache and it's not recorded, resulting in CI failures where there is no cache.

@sean797
Copy link
Member

sean797 commented Apr 18, 2019

Cons

  • we're losing typechecking of params, as these are not exposed in the API JSON and apypie has no further knowledge about them
  • we're losing relationships between entities, so the modules will need to potentially pull more data themself.

Could we extend apipie and by extension apypie to know about these? I imagine that would take a farily long time to do, but it would be awesome if Foreman and its plugins had a 100% self docuementing API

@ekohl
Copy link
Member

ekohl commented Apr 18, 2019

Rather than extending apipie's custom format, I'd rather invest in moving to the OpenAPI spec. apipie can already output that, but it's likely we need to enhance some things for better output.

@mdellweg
Copy link
Member

Is there any heuristic to check for an outdated apypie cache?
What happens if you fuel two foremans with different versions in the same playbook?

@evgeni
Copy link
Member Author

evgeni commented Apr 26, 2019

@mdellweg yes. The cache is url based, so it will not clash. And apypie checks the last modified header too.

@evgeni evgeni marked this pull request as ready for review April 30, 2019 06:05
@evgeni
Copy link
Member Author

evgeni commented Apr 30, 2019

Given all the emoji reactions to the initial PR comment, I'd think y'all are in favor of this, but can you express that a bit more explicitly please? :)

@mdellweg
Copy link
Member

I know we had the discussion about using nailgun. So the question that comes to my mind is: Will this be adopted as a successor of nailgun and also be used in robottelo?
Thinking only about foreman-ansible-modules, i like the approach.

@akofink
Copy link
Contributor

akofink commented Apr 30, 2019

The way I see it, QE is, and operates like, a different team from the Katello/Foreman developers. There's a set of projects for using with Foreman in production (plugins, cli, ansible playbooks, etc.) which Foreman devs work on and a set of repositories used to test Satellite by QE (nailgun, robottelo, airgun, etc.) that QEs work on. This is the problem with Nailgun IMO - it's a QE utility, not a tested and vetted production-use Foreman API python library. The GitHub org which these utilities live is SatelliteQE - clearly not intended for upstream use.

A secondary related issue with Nailgun is that it's "release cycle" (if you'd call it that) is tied to Satellite, not Foreman. We'd need buy in from QE to uproot the status quo and start tracking upstream releases, which happen 3X more frequently than downstream. If you didn't already guess, they are already very busy.

Lastly, there's some packaging issue with Nailgun that has been previously mentioned due to one of its dependencies (python requests? idk).

So, yes, 🚀 for a python library that is automatically built from apipie. When something changes upstream, there's no code to write or update, just an API doc to generate (and tests to re-run).

@ekohl
Copy link
Member

ekohl commented Apr 30, 2019

Nailgun is, as I understand it, much more focused at verifying the API. This library is intended to just consume what's there. Perhaps Nailgun could even use apypie underneath, but that verification still needs to happen.

@ehelms
Copy link
Member

ehelms commented May 10, 2019

Generally 👍 to this approach as it also feels like it will make cross-version usage more robust.

How much work do you estimate it is to convert all entities?

@mdellweg
Copy link
Member

I actually think, entities can be migrated gradually. We can track this process in a new checklist issue.

@evgeni
Copy link
Member Author

evgeni commented May 13, 2019

Ad @mdellweg's question: nailgun is an API client that has it's primary usecase in testing said API. As such it cannot rely on the API to be correct, but needs to "know" endpoints, entities and data structures itself. As such, apypie is not a replacement for nailgun in all use-cases and I think robottelo is one of these who will remain on nailgun.

The packaging issue @akofink mentioned is not about requests but blinker_herald and inflection, as these are not packaged today. It's not that much of an issue as an nuisance, as noone wants to maintain more packages than absolutely required.

Ad @ehelms question: As you can see from this PR, the diff for the actual module is quite small, but you'll need to run the tests against a live system a few times to get all the details right and I wouldn't be surprised if we find a few bugs while porting the first entities. Overall the amount of work is somehwere in the 30-60 minutes per entity, so not that much. And as @mdellweg wrote, this can (and should) happen gradually. (And I'd not convert all of them, just the ones covered by tests ;))

@mdellweg
Copy link
Member

(And I'd not convert all of them, just the ones covered by tests ;))

...which is the vast majority:
#77

(also i don't want to stop promoting this issue ;))

@ehelms
Copy link
Member

ehelms commented May 14, 2019

I think then my ask would be to snapshot the current pure nailgun either via establishing a version and thus release for the modules or simply branching the current state and then merging this with an eye towards a release milestone that is fully converted to apypie.

@mdellweg
Copy link
Member

I have one last concern before we can actively start to bring this forward: How would the fact module look like? I think it is the one that is fundamentally different and we should at least have something equivalent.

@evgeni
Copy link
Member Author

evgeni commented May 15, 2019

@mdellweg that's a fair ask, I'll prep the facts module too, so you'll have an example :)

@evgeni
Copy link
Member Author

evgeni commented May 15, 2019

@mdellweg there we go, I still need to re-record the VCR tapes, but it's working.

please note, this "breaks" the api, as now we have to write settings instead of Setting (nailgun uses singular names, where as Foreman does not)



def main():

module = ForemanAnsibleModule(
module = ForemanApypieAnsibleModule(
Copy link
Member

Choose a reason for hiding this comment

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

Is this name going to stick, once we ridded nailgun?

Copy link
Member Author

Choose a reason for hiding this comment

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

As soon as we're back to one backend, we can switch the naming back. I don't mind either way.

@mdellweg
Copy link
Member

mdellweg commented May 15, 2019

please note, this "breaks" the api, as now we have to write settings instead of Setting (nailgun uses singular names, where as Foreman does not)

I think i am ok with this. It is an adequate replacement. Thanks for taking that concern seriously.

return_length: 0
- name: Run with invalid resource
Copy link
Member Author

Choose a reason for hiding this comment

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

this is currently not supported with apypie and will need some love there.

Copy link
Member

Choose a reason for hiding this comment

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

OK for me.

Copy link
Member

Choose a reason for hiding this comment

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

For the sake of clarity, how will users know what resources are valid? I assume they won't today and we can address that once we have made the required changes in apypie?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct. My plan is to report back the available resources in the error message from apypie when a non-available one is called, and then we can restore the code we had.

@evgeni
Copy link
Member Author

evgeni commented May 16, 2019

@ehelms I've created a nailgun branch which is at the same level as current master: https://github.com/theforeman/foreman-ansible-modules/tree/nailgun

@sean797
Copy link
Member

sean797 commented May 16, 2019

please note, this "breaks" the api, as now we have to write settings instead of Setting (nailgun uses singular names, where as Foreman does not)

I too am also fine with that, considering we are doing a new major release.

With this PR, could you add something prominent to the README so users know about that branch?

@evgeni
Copy link
Member Author

evgeni commented May 17, 2019

@sean797 done

changed, _ = self.ensure_resource(resource, entity_dict, entity, state, check_missing, check_type, force_update)
return changed

def ensure_resource(self, resource, entity_dict, entity, state, check_missing=None, check_type=None, force_update=None):
Copy link
Member

Choose a reason for hiding this comment

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

I can only speculate, you didn't like the name naildown_entity enough...

Copy link
Member Author

Choose a reason for hiding this comment

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

I just didn't want the problem to look like a nail ;)

@evgeni evgeni force-pushed the apypie branch 2 times, most recently from a03fbcd to c7694dd Compare May 20, 2019 15:43
@evgeni
Copy link
Member Author

evgeni commented May 20, 2019

this should pass tests (again) and has a more or less clean history

so will merge tomorrow when back in the office :)

@evgeni evgeni changed the title [RFC] try out using apypie instead of nailgun port foreman_domain, foreman_search_facts and katello_content_credential to apypie May 21, 2019
@evgeni evgeni merged commit c45f41c into theforeman:master May 21, 2019
@evgeni evgeni deleted the apypie branch May 21, 2019 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants