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

Add Foreman inventory #19510

Merged
merged 1 commit into from
Feb 21, 2017
Merged

Add Foreman inventory #19510

merged 1 commit into from
Feb 21, 2017

Conversation

dLobatog
Copy link
Contributor

ISSUE TYPE
  • Feature Pull Request
COMPONENT NAME

external inventories

ANSIBLE VERSION
ansible 2.2.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
SUMMARY

Adds an inventory to the list of external inventories in contrib. This inventory allows Ansible users to query information from Foreman and Satellite 6 (https://github.com/theforeman/foreman_ansible_inventory).

Should I add the README here too?

@ansibot ansibot added affects_2.3 This issue/PR affects Ansible v2.3 feature_pull_request labels Dec 19, 2016
@chrismeyersfsu
Copy link
Member

@dLobatog please sprinkle the docs https://github.com/theforeman/foreman_ansible_inventory into the .ini file.

self.facts = dict() # Facts of each host
self.hostgroups = dict() # host groups

def run(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Typically we organize code so that callers are closer to the bottom and callees are near the top (__init__ being the exception... it's always at the top). This organization puts methods like run() near the bottom of the class and helpers above it.

import os
import re
import requests
from requests.auth import HTTPBasicAuth
Copy link
Contributor

Choose a reason for hiding this comment

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

Note, we try to organize imports into three blocks (all optional). stdlib imports at the top, then third party library imports (requests in this case), and finally any imports from ansible itself.

cache.write(json_data)
cache.close()

@staticmethod
Copy link
Contributor

Choose a reason for hiding this comment

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

Helper functions like this (Ones that don't use self. Ones that could be marked @staticmethod if they were part of the class). should typically be placed at the toplevel. So far I see to_safe, json_format_dict, and push.

try:
import json
except ImportError:
import simplejson as json
Copy link
Contributor

Choose a reason for hiding this comment

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

The json library entered the python stdlib with python-2.6. Code that runs on the controller (which includes dynamic inventory scripts) need python-2.6 or greater. So there's no need to fallback to simplejson here. the json stdlib library should always be available.

try:
import ConfigParser
except ImportError:
import configparser as ConfigParser
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment to explain python2 vs python3 would be good here.

@ansibot ansibot added needs_triage Needs a first human triage before being processed. and removed triage labels Dec 19, 2016


class ForemanInventory(object):
config_paths = [
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 really matter since only a single instance of ForemanInventory is created in this script but config_paths should really be an instance attribute (created and given an initial value in __init__) rather than a class attribute. Class attributes exist a single time per class. All instances of the class would share the same instance of that attribute. Instance attributes exist, one per instance and thus can change independently. Modifying config_paths later in the code is a tip-off that this should be an instance-attribute rather than a class-attribute.

Also, you can probably move the setting from an environment variable to be with this code when you do that. It would seem to make sense to keep that all together.

import copy
import os
import re
import requests
Copy link
Member

Choose a reason for hiding this comment

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

it would be better to avoid the requests lib, but if you cannot you should at least specify a min version.

@bcoca bcoca removed the needs_triage Needs a first human triage before being processed. label Dec 19, 2016
@abadger
Copy link
Contributor

abadger commented Dec 19, 2016

Made some easy to fix style comments. The main logic looks like it will do what it's supposed to. It looks like there could be some larger changes that could be made (for instance, I think that --host only affects output here... It does not affect what has to be fetched from foreman). As long as you're continuing to maintain it, I don't think that's a blocker for getting this merged... more of a note about what could be fixed in the future.

@abadger
Copy link
Contributor

abadger commented Dec 19, 2016

needs_revision

@ansibot ansibot added needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. and removed needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. labels Dec 19, 2016
@ansibot ansibot added the needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. label Dec 21, 2016
@dLobatog
Copy link
Contributor Author

@abadger Thanks for taking a look - not a Python expert myself, but I think I've addressed most of the comments. The one I wasn't so sure about is @bcoca 's ask to pin requests to a version. I think 1.1 (what CentOS 6 has) is probably safe, but I'm not sure if you wanted something different.

Hope this works - I'll see if I can remove requests from here at some point even, it was just.. convenient at the time. Thanks @chrismeyersfsu @abadger @bcoca for taking a look

@dLobatog
Copy link
Contributor Author

BTW CI is picking on a problem on ./lib/ansible/modules/network/ldap_entry.py , not foreman.py

regex = "[^A-Za-z0-9\_]"
return re.sub(regex, "_", word.replace(" ", ""))

def json_format_dict(self, data, pretty=False):
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a @staticmethod and self removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, better would be to move this to the toplevel as a regular function

else:
return json.dumps(data)

def push(self, d, k, v):
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be an @staticmethod nad self removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, make this a regular function

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually... Even better: use a defaultdict instead of this. This is how defaultdicts work:

from collections import defaultdict

foo = defaultdict(list)
foo[k].append(v)

else:
d[k] = [v]

def run(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be the last method in the class.

self.config_paths.append(os.path.expanduser(os.path.expandvars(env_value)))

@staticmethod
def to_safe(word):
Copy link
Contributor

Choose a reason for hiding this comment

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

Make this a regular function

self._print_data()
return True

def _read_settings(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

This method doesn't seem to do much. I would merge it into the run() method.

  • read_settings and parse_cli_args don't seem related. They don't operate on the same instance attributes, for instance.
  • It's a bit funny to have a private method (_read_settings()) calling public methods (read_settings() and parse_cli_args()) that aren't usable by anything else.
  • If this is merged into run(), it will only add one more line there and will remove 5 lines overall.

except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
group_patterns = "[]"

self.group_patterns = eval(group_patterns)
Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't notice this before but you have eval here... What is that for? It's rather dangerous as it means if someone can get code into the config file for the foreman dynamic inventory, they can then execute whatever code they want as the user running ansible. We should get rid of this if at all possible.

self.parse_cli_args()
return True

def _get_inventory(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for this to be a private method. rename to get_inventory().

def _get_hosts(self):
return self._get_json("%s/api/v2/hosts" % self.foreman_url)

def _get_hostgroup_by_id(self, hid):
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 seem to be called from anywhere. Should it be deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it was used to resolve Foreman host parameters on theforeman/foreman_ansible_inventory@9972f3f#diff-2b3c7ba3ec283a3b380cff70f61e0290R144 , not needed anymore

if self.args.refresh_cache:
self.update_cache()
elif not self.is_cache_valid():
self.update_cache()
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably better to write this as

if self.args.refresh_cache or not self.is_cache_valid():
    self.update_cache()

@abadger
Copy link
Contributor

abadger commented Jan 5, 2017

This is probably what the order of methods should be inside of the class:

  • init()
  • read_settings()
  • parse_cli_args()
  • _get_json()
  • _get_hosts()
  • _get_all_params_by_id()
  • _resolve_params()
  • _get_facts_by_id()
  • _get_facts()
  • write_to_cache()
  • _write_cache()
  • update_cache()
  • is_cache_valid()
  • load_inventory_from_cache()
  • load_params_from_cache()
  • load_facts_from_cache()
  • load_cache_from_cache()
  • _get_inventory()
  • get_host_info()
  • _print_data()
  • run()

This puts the entrypoint at the bottom and then everything that helps it to run is above it.

@abadger
Copy link
Contributor

abadger commented Jan 5, 2017

Okay, I think I've gone over this sufficiently that this is everything that needs to be changed.

@dLobatog
Copy link
Contributor Author

@abadger Thanks so much for the detailed review! I think I've gone through each of the things you've commented on , with the exception of #19510 (comment) - I didn't know what you referred to, it looks like a regular function already?

The latest push contains the updates to your comments, including order of methods, and the improvements @wenottingham made here theforeman/foreman_ansible_inventory@7a55c62

@abadger
Copy link
Contributor

abadger commented Jan 31, 2017

@dLobatog Right now it's a method. My comment is to make it a function instead.

This commit adds the foreman inventory based on
https://github.com/theforeman/foreman_ansible_inventory
and its configuration file.
@dLobatog
Copy link
Contributor Author

dLobatog commented Feb 9, 2017

@abadger Updated - the json_format_dict function is now out of the ForemanInventory class. I wasn't sure where to put it so I put it on top. Thanks for the review!

@abadger abadger merged commit 01436cf into ansible:devel Feb 21, 2017
@abadger
Copy link
Contributor

abadger commented Feb 21, 2017

Merged to devel for inclusion in 2.3.0. Thaks @dLobatog !

@dLobatog
Copy link
Contributor Author

Thanks @abadger for the review!

@ansibot ansibot added feature This issue/PR relates to a feature request. and removed feature_pull_request labels Mar 4, 2018
@dagwieers dagwieers added the foreman Foreman community label Feb 8, 2019
@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.3 This issue/PR affects Ansible v2.3 c:inventory/contrib_script feature This issue/PR relates to a feature request. foreman Foreman community needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants