diff options
63 files changed, 1028 insertions, 455 deletions
diff --git a/.tito/packages/openshift-ansible b/.tito/packages/openshift-ansible index 92f545b25..6046a1a86 100644 --- a/.tito/packages/openshift-ansible +++ b/.tito/packages/openshift-ansible @@ -1 +1 @@ -3.0.6-1 ./ +3.0.7-1 ./ diff --git a/README_OSE.md b/README_OSE.md index 79ad07044..524950d51 100644 --- a/README_OSE.md +++ b/README_OSE.md @@ -79,9 +79,6 @@ ansible_ssh_user=root # To deploy origin, change deployment_type to origin deployment_type=enterprise -# Pre-release registry URL -oreg_url=rcm-img-docker01.build.eng.bos.redhat.com:5001/openshift3/ose-${component}:${version} - # Pre-release additional repo openshift_additional_repos=[{'id': 'ose-devel', 'name': 'ose-devel', 'baseurl': diff --git a/bin/README_SHELL_COMPLETION b/bin/README_SHELL_COMPLETION index 5f05df7fc..49bba3acc 100644 --- a/bin/README_SHELL_COMPLETION +++ b/bin/README_SHELL_COMPLETION @@ -14,7 +14,7 @@ will populate the cache file and the completions should become available. This script will look at the cached version of your -multi_ec2 results in ~/.ansible/tmp/multi_ec2_inventory.cache. +multi_inventory results in ~/.ansible/tmp/multi_inventory.cache. It will then parse a few {host}.{env} out of the json and return them to be completable. diff --git a/bin/openshift_ansible.conf.example b/bin/openshift_ansible.conf.example index e891b855a..8786dfc13 100644 --- a/bin/openshift_ansible.conf.example +++ b/bin/openshift_ansible.conf.example @@ -1,5 +1,5 @@ #[main] -#inventory = /usr/share/ansible/inventory/multi_ec2.py +#inventory = /usr/share/ansible/inventory/multi_inventory.py #[host_type_aliases] #host-type-one = aliasa,aliasb diff --git a/bin/openshift_ansible/awsutil.py b/bin/openshift_ansible/awsutil.py index 9df034f57..45345007c 100644 --- a/bin/openshift_ansible/awsutil.py +++ b/bin/openshift_ansible/awsutil.py @@ -4,7 +4,10 @@ import os import re -from openshift_ansible import multi_ec2 + +# Buildbot does not have multi_inventory installed +#pylint: disable=no-name-in-module +from openshift_ansible import multi_inventory class ArgumentError(Exception): """This class is raised when improper arguments are passed.""" @@ -49,9 +52,9 @@ class AwsUtil(object): Keyword arguments: args -- optional arguments to pass to the inventory script """ - mec2 = multi_ec2.MultiEc2(args) - mec2.run() - return mec2.result + minv = multi_inventory.MultiInventory(args) + minv.run() + return minv.result def get_environments(self): """Searches for env tags in the inventory and returns all of the envs found.""" diff --git a/bin/ossh_bash_completion b/bin/ossh_bash_completion index 5072161f0..997ff0f9c 100755 --- a/bin/ossh_bash_completion +++ b/bin/ossh_bash_completion @@ -1,12 +1,12 @@ __ossh_known_hosts(){ if python -c 'import openshift_ansible' &>/dev/null; then - /usr/bin/python -c 'from openshift_ansible import multi_ec2; m=multi_ec2.MultiEc2(); m.run(); z=m.result; print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])' + /usr/bin/python -c 'from openshift_ansible import multi_inventory; m=multi_inventory.MultiInventory(); m.run(); z=m.result; print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])' - elif [[ -f /dev/shm/.ansible/tmp/multi_ec2_inventory.cache ]]; then - /usr/bin/python -c 'import json; loc="/dev/shm/.ansible/tmp/multi_ec2_inventory.cache"; z=json.loads(open(loc).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])' + elif [[ -f /dev/shm/.ansible/tmp/multi_inventory.cache ]]; then + /usr/bin/python -c 'import json; loc="/dev/shm/.ansible/tmp/multi_inventory.cache"; z=json.loads(open(loc).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])' - elif [[ -f ~/.ansible/tmp/multi_ec2_inventory.cache ]]; then - /usr/bin/python -c 'import json,os; loc="%s" % os.path.expanduser("~/.ansible/tmp/multi_ec2_inventory.cache"); z=json.loads(open(loc).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])' + elif [[ -f ~/.ansible/tmp/multi_inventory.cache ]]; then + /usr/bin/python -c 'import json,os; loc="%s" % os.path.expanduser("~/.ansible/tmp/multi_inventory.cache"); z=json.loads(open(loc).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])' fi } @@ -26,13 +26,13 @@ complete -F _ossh ossh oscp __opssh_known_hosts(){ if python -c 'import openshift_ansible' &>/dev/null; then - /usr/bin/python -c 'from openshift_ansible.multi_ec2 import MultiEc2; m=MultiEc2(); m.run(); print "\n".join(["%s" % (host["ec2_tag_host-type"]) for dns, host in m.result["_meta"]["hostvars"].items() if "ec2_tag_host-type" in host])' + /usr/bin/python -c 'from openshift_ansible.multi_inventory import MultiInventory; m=MultiInventory(); m.run(); print "\n".join(["%s" % (host["ec2_tag_host-type"]) for dns, host in m.result["_meta"]["hostvars"].items() if "ec2_tag_host-type" in host])' - elif [[ -f /dev/shm/.ansible/tmp/multi_ec2_inventory.cache ]]; then - /usr/bin/python -c 'import json; loc="/dev/shm/.ansible/tmp/multi_ec2_inventory.cache"; z=json.loads(open(loc).read()); print "\n".join(["%s" % (host["ec2_tag_host-type"]) for dns, host in z["_meta"]["hostvars"].items() if "ec2_tag_host-type" in host])' + elif [[ -f /dev/shm/.ansible/tmp/multi_inventory.cache ]]; then + /usr/bin/python -c 'import json; loc="/dev/shm/.ansible/tmp/multi_inventory.cache"; z=json.loads(open(loc).read()); print "\n".join(["%s" % (host["ec2_tag_host-type"]) for dns, host in z["_meta"]["hostvars"].items() if "ec2_tag_host-type" in host])' - elif [[ -f ~/.ansible/tmp/multi_ec2_inventory.cache ]]; then - /usr/bin/python -c 'import json,os; loc="%s" % os.path.expanduser("/dev/shm/.ansible/tmp/multi_ec2_inventory.cache"); z=json.loads(open(loc).read()); print "\n".join(["%s" % (host["ec2_tag_host-type"]) for dns, host in z["_meta"]["hostvars"].items() if "ec2_tag_host-type" in host])' + elif [[ -f ~/.ansible/tmp/multi_inventory.cache ]]; then + /usr/bin/python -c 'import json,os; loc="%s" % os.path.expanduser("/dev/shm/.ansible/tmp/multi_inventory.cache"); z=json.loads(open(loc).read()); print "\n".join(["%s" % (host["ec2_tag_host-type"]) for dns, host in z["_meta"]["hostvars"].items() if "ec2_tag_host-type" in host])' fi } diff --git a/bin/ossh_zsh_completion b/bin/ossh_zsh_completion index 44500c618..3c4018636 100644 --- a/bin/ossh_zsh_completion +++ b/bin/ossh_zsh_completion @@ -2,13 +2,13 @@ _ossh_known_hosts(){ if python -c 'import openshift_ansible' &>/dev/null; then - print $(/usr/bin/python -c 'from openshift_ansible import multi_ec2; m=multi_ec2.MultiEc2(); m.run(); z=m.result; print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])') + print $(/usr/bin/python -c 'from openshift_ansible import multi_inventory; m=multi_inventory.MultiInventory(); m.run(); z=m.result; print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])') - elif [[ -f /dev/shm/.ansible/tmp/multi_ec2_inventory.cache ]]; then - print $(/usr/bin/python -c 'import json; loc="/dev/shm/.ansible/tmp/multi_ec2_inventory.cache"; z=json.loads(open(loc).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])') + elif [[ -f /dev/shm/.ansible/tmp/multi_inventory.cache ]]; then + print $(/usr/bin/python -c 'import json; loc="/dev/shm/.ansible/tmp/multi_inventory.cache"; z=json.loads(open(loc).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])') - elif [[ -f ~/.ansible/tmp/multi_ec2_inventory.cache ]]; then - print $(/usr/bin/python -c 'import json,os; loc="%s" % os.path.expanduser("~/.ansible/tmp/multi_ec2_inventory.cache"); z=json.loads(open(loc).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])') + elif [[ -f ~/.ansible/tmp/multi_inventory.cache ]]; then + print $(/usr/bin/python -c 'import json,os; loc="%s" % os.path.expanduser("~/.ansible/tmp/multi_inventory.cache"); z=json.loads(open(loc).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items() if all(k in host for k in ("ec2_tag_Name", "ec2_tag_environment"))])') fi diff --git a/bin/zsh_functions/_ossh b/bin/zsh_functions/_ossh index 7c6cb7b0b..d205e1055 100644 --- a/bin/zsh_functions/_ossh +++ b/bin/zsh_functions/_ossh @@ -1,8 +1,8 @@ #compdef ossh oscp _ossh_known_hosts(){ - if [[ -f ~/.ansible/tmp/multi_ec2_inventory.cache ]]; then - print $(/usr/bin/python -c 'import json,os; z = json.loads(open("%s"%os.path.expanduser("~/.ansible/tmp/multi_ec2_inventory.cache")).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items()])') + if [[ -f ~/.ansible/tmp/multi_inventory.cache ]]; then + print $(/usr/bin/python -c 'import json,os; z = json.loads(open("%s"%os.path.expanduser("~/.ansible/tmp/multi_inventory.cache")).read()); print "\n".join(["%s.%s" % (host["ec2_tag_Name"],host["ec2_tag_environment"]) for dns, host in z["_meta"]["hostvars"].items()])') fi } diff --git a/filter_plugins/oo_filters.py b/filter_plugins/oo_filters.py index a57b0f895..dfd9a111e 100644 --- a/filter_plugins/oo_filters.py +++ b/filter_plugins/oo_filters.py @@ -7,6 +7,8 @@ Custom filters for use in openshift-ansible from ansible import errors from operator import itemgetter +import OpenSSL.crypto +import os.path import pdb import re import json @@ -327,6 +329,68 @@ class FilterModule(object): return revamped_outputs + @staticmethod + # pylint: disable=too-many-branches + def oo_parse_certificate_names(certificates, data_dir, internal_hostnames): + ''' Parses names from list of certificate hashes. + + Ex: certificates = [{ "certfile": "/etc/origin/master/custom1.crt", + "keyfile": "/etc/origin/master/custom1.key" }, + { "certfile": "custom2.crt", + "keyfile": "custom2.key" }] + + returns [{ "certfile": "/etc/origin/master/custom1.crt", + "keyfile": "/etc/origin/master/custom1.key", + "names": [ "public-master-host.com", + "other-master-host.com" ] }, + { "certfile": "/etc/origin/master/custom2.crt", + "keyfile": "/etc/origin/master/custom2.key", + "names": [ "some-hostname.com" ] }] + ''' + if not issubclass(type(certificates), list): + raise errors.AnsibleFilterError("|failed expects certificates is a list") + + if not issubclass(type(data_dir), unicode): + raise errors.AnsibleFilterError("|failed expects data_dir is unicode") + + if not issubclass(type(internal_hostnames), list): + raise errors.AnsibleFilterError("|failed expects internal_hostnames is list") + + for certificate in certificates: + if 'names' in certificate.keys(): + continue + else: + certificate['names'] = [] + + if not os.path.isfile(certificate['certfile']) and not os.path.isfile(certificate['keyfile']): + # Unable to find cert/key, try to prepend data_dir to paths + certificate['certfile'] = os.path.join(data_dir, certificate['certfile']) + certificate['keyfile'] = os.path.join(data_dir, certificate['keyfile']) + if not os.path.isfile(certificate['certfile']) and not os.path.isfile(certificate['keyfile']): + # Unable to find cert/key in data_dir + raise errors.AnsibleFilterError("|certificate and/or key does not exist '%s', '%s'" % + (certificate['certfile'], certificate['keyfile'])) + + try: + st_cert = open(certificate['certfile'], 'rt').read() + cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, st_cert) + certificate['names'].append(str(cert.get_subject().commonName.decode())) + for i in range(cert.get_extension_count()): + if cert.get_extension(i).get_short_name() == 'subjectAltName': + for name in str(cert.get_extension(i)).replace('DNS:', '').split(', '): + certificate['names'].append(name) + except: + raise errors.AnsibleFilterError(("|failed to parse certificate '%s', " % certificate['certfile'] + + "please specify certificate names in host inventory")) + + certificate['names'] = [name for name in certificate['names'] if name not in internal_hostnames] + certificate['names'] = list(set(certificate['names'])) + if not certificate['names']: + raise errors.AnsibleFilterError(("|failed to parse certificate '%s' or " % certificate['certfile'] + + "detected a collision with internal hostname, please specify " + + "certificate names in host inventory")) + return certificates + def filters(self): ''' returns a mapping of filters to methods ''' return { @@ -342,5 +406,6 @@ class FilterModule(object): "oo_combine_dict": self.oo_combine_dict, "oo_split": self.oo_split, "oo_filter_list": self.oo_filter_list, - "oo_parse_heat_stack_outputs": self.oo_parse_heat_stack_outputs + "oo_parse_heat_stack_outputs": self.oo_parse_heat_stack_outputs, + "oo_parse_certificate_names": self.oo_parse_certificate_names } diff --git a/inventory/byo/hosts.example b/inventory/byo/hosts.example index 8a5d5c719..f60918e6d 100644 --- a/inventory/byo/hosts.example +++ b/inventory/byo/hosts.example @@ -24,7 +24,7 @@ deployment_type=atomic-enterprise #use_cluster_metrics=true # Pre-release registry URL -#oreg_url=rcm-img-docker01.build.eng.bos.redhat.com:5001/openshift3/ose-${component}:${version} +#oreg_url=example.com/openshift3/ose-${component}:${version} # Pre-release Dev puddle repo #openshift_additional_repos=[{'id': 'ose-devel', 'name': 'ose-devel', 'baseurl': 'http://buildvm-devops.usersys.redhat.com/puddle/build/OpenShiftEnterprise/3.0/latest/RH7-RHOSE-3.0/$basearch/os', 'enabled': 1, 'gpgcheck': 0}] @@ -99,6 +99,11 @@ openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true', # set RPM version for debugging purposes #openshift_pkg_version=-3.0.0.0 +# Configure custom master certificates +#openshift_master_named_certificates=[{"certfile": "/path/to/custom1.crt", "keyfile": "/path/to/custom1.key"}] +# Detected names may be overridden by specifying the "names" key +#openshift_master_named_certificates=[{"certfile": "/path/to/custom1.crt", "keyfile": "/path/to/custom1.key", "names": ["public-master-host.com"]}] + # host group for masters [masters] ose3-master[1:3]-ansible.test.example.com diff --git a/inventory/gce/hosts/gce.py b/inventory/gce/hosts/gce.py index 6ed12e011..99746cdbf 100755 --- a/inventory/gce/hosts/gce.py +++ b/inventory/gce/hosts/gce.py @@ -66,12 +66,22 @@ Examples: $ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a" Use the GCE inventory script to print out instance specific information - $ plugins/inventory/gce.py --host my_instance + $ contrib/inventory/gce.py --host my_instance Author: Eric Johnson <erjohnso@google.com> Version: 0.0.1 ''' +__requires__ = ['pycrypto>=2.6'] +try: + import pkg_resources +except ImportError: + # Use pkg_resources to find the correct versions of libraries and set + # sys.path appropriately when there are multiversion installs. We don't + # fail here as there is code that better expresses the errors where the + # library is used. + pass + USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin" USER_AGENT_VERSION="v1" @@ -102,9 +112,9 @@ class GceInventory(object): # Just display data for specific host if self.args.host: - print self.json_format_dict(self.node_to_dict( + print(self.json_format_dict(self.node_to_dict( self.get_instance(self.args.host)), - pretty=self.args.pretty) + pretty=self.args.pretty)) sys.exit(0) # Otherwise, assume user wants all instances grouped @@ -120,7 +130,6 @@ class GceInventory(object): os.path.dirname(os.path.realpath(__file__)), "gce.ini") gce_ini_path = os.environ.get('GCE_INI_PATH', gce_ini_default_path) - # Create a ConfigParser. # This provides empty defaults to each key, so that environment # variable configuration (as opposed to INI configuration) is able @@ -174,7 +183,6 @@ class GceInventory(object): args[1] = os.environ.get('GCE_PEM_FILE_PATH', args[1]) kwargs['project'] = os.environ.get('GCE_PROJECT', kwargs['project']) - # Retrieve and return the GCE driver. gce = get_driver(Provider.GCE)(*args, **kwargs) gce.connection.user_agent_append( @@ -213,8 +221,7 @@ class GceInventory(object): 'gce_image': inst.image, 'gce_machine_type': inst.size, 'gce_private_ip': inst.private_ips[0], - # Hosts don't always have a public IP name - #'gce_public_ip': inst.public_ips[0], + 'gce_public_ip': inst.public_ips[0] if len(inst.public_ips) >= 1 else None, 'gce_name': inst.name, 'gce_description': inst.extra['description'], 'gce_status': inst.extra['status'], @@ -222,15 +229,15 @@ class GceInventory(object): 'gce_tags': inst.extra['tags'], 'gce_metadata': md, 'gce_network': net, - # Hosts don't always have a public IP name - #'ansible_ssh_host': inst.public_ips[0] + # Hosts don't have a public name, so we add an IP + 'ansible_ssh_host': inst.public_ips[0] if len(inst.public_ips) >= 1 else inst.private_ips[0] } def get_instance(self, instance_name): '''Gets details about a specific instance ''' try: return self.driver.ex_get_node(instance_name) - except Exception, e: + except Exception as e: return None def group_instances(self): @@ -250,7 +257,10 @@ class GceInventory(object): tags = node.extra['tags'] for t in tags: - tag = 'tag_%s' % t + if t.startswith('group-'): + tag = t[6:] + else: + tag = 'tag_%s' % t if groups.has_key(tag): groups[tag].append(name) else: groups[tag] = [name] diff --git a/inventory/multi_ec2.yaml.example b/inventory/multi_ec2.yaml.example deleted file mode 100644 index bbd81ad20..000000000 --- a/inventory/multi_ec2.yaml.example +++ /dev/null @@ -1,32 +0,0 @@ -# multi ec2 inventory configs -# -cache_location: ~/.ansible/tmp/multi_ec2_inventory.cache - -accounts: - - name: aws1 - provider: aws/hosts/ec2.py - provider_config: - ec2: - regions: all - regions_exclude: us-gov-west-1,cn-north-1 - destination_variable: public_dns_name - route53: False - cache_path: ~/.ansible/tmp - cache_max_age: 300 - vpc_destination_variable: ip_address - env_vars: - AWS_ACCESS_KEY_ID: XXXXXXXXXXXXXXXXXXXX - AWS_SECRET_ACCESS_KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - all_group: ec2 - extra_vars: - cloud: aws - account: aws1 - -- name: aws2 - provider: aws/hosts/ec2.py - env_vars: - AWS_ACCESS_KEY_ID: XXXXXXXXXXXXXXXXXXXX - AWS_SECRET_ACCESS_KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - EC2_INI_PATH: /etc/ansible/ec2.ini - -cache_max_age: 60 diff --git a/inventory/multi_ec2.py b/inventory/multi_inventory.py index 98dde3f3c..354a8c10c 100755 --- a/inventory/multi_ec2.py +++ b/inventory/multi_inventory.py @@ -1,6 +1,6 @@ #!/usr/bin/env python2 ''' - Fetch and combine multiple ec2 account settings into a single + Fetch and combine multiple inventory account settings into a single json hash. ''' # vim: expandtab:tabstop=4:shiftwidth=4 @@ -15,13 +15,19 @@ import errno import fcntl import tempfile import copy +from string import Template +import shutil -CONFIG_FILE_NAME = 'multi_ec2.yaml' -DEFAULT_CACHE_PATH = os.path.expanduser('~/.ansible/tmp/multi_ec2_inventory.cache') +CONFIG_FILE_NAME = 'multi_inventory.yaml' +DEFAULT_CACHE_PATH = os.path.expanduser('~/.ansible/tmp/multi_inventory.cache') -class MultiEc2(object): +class MultiInventoryException(Exception): + '''Exceptions for MultiInventory class''' + pass + +class MultiInventory(object): ''' - MultiEc2 class: + MultiInventory class: Opens a yaml config file and reads aws credentials. Stores a json hash of resources in result. ''' @@ -35,7 +41,7 @@ class MultiEc2(object): self.cache_path = DEFAULT_CACHE_PATH self.config = None - self.all_ec2_results = {} + self.all_inventory_results = {} self.result = {} self.file_path = os.path.join(os.path.dirname(os.path.realpath(__file__))) @@ -56,7 +62,7 @@ class MultiEc2(object): cache is valid for the inventory. if the cache is valid; return cache - else the credentials are loaded from multi_ec2.yaml or from the env + else the credentials are loaded from multi_inventory.yaml or from the env and we attempt to get the inventory from the provider specified. ''' # load yaml @@ -111,6 +117,10 @@ class MultiEc2(object): with open(conf_file) as conf: config = yaml.safe_load(conf) + # Provide a check for unique account names + if len(set([acc['name'] for acc in config['accounts']])) != len(config['accounts']): + raise MultiInventoryException('Duplicate account names in config file') + return config def get_provider_tags(self, provider, env=None): @@ -136,23 +146,25 @@ class MultiEc2(object): else: cmds.append('--list') - cmds.append('--refresh-cache') + if 'aws' in provider.lower(): + cmds.append('--refresh-cache') return subprocess.Popen(cmds, stderr=subprocess.PIPE, \ stdout=subprocess.PIPE, env=env) @staticmethod - def generate_config(config_data): - """Generate the ec2.ini file in as a secure temp file. - Once generated, pass it to the ec2.py as an environment variable. + def generate_config(provider_files): + """Generate the provider_files in a temporary directory. """ - fildes, tmp_file_path = tempfile.mkstemp(prefix='multi_ec2.ini.') - for section, values in config_data.items(): - os.write(fildes, "[%s]\n" % section) - for option, value in values.items(): - os.write(fildes, "%s = %s\n" % (option, value)) - os.close(fildes) - return tmp_file_path + prefix = 'multi_inventory.' + tmp_dir_path = tempfile.mkdtemp(prefix=prefix) + for provider_file in provider_files: + filedes = open(os.path.join(tmp_dir_path, provider_file['name']), 'w+') + content = Template(provider_file['contents']).substitute(tmpdir=tmp_dir_path) + filedes.write(content) + filedes.close() + + return tmp_dir_path def run_provider(self): '''Setup the provider call with proper variables @@ -160,13 +172,21 @@ class MultiEc2(object): ''' try: all_results = [] - tmp_file_paths = [] + tmp_dir_paths = [] processes = {} for account in self.config['accounts']: - env = account['env_vars'] - if account.has_key('provider_config'): - tmp_file_paths.append(MultiEc2.generate_config(account['provider_config'])) - env['EC2_INI_PATH'] = tmp_file_paths[-1] + tmp_dir = None + if account.has_key('provider_files'): + tmp_dir = MultiInventory.generate_config(account['provider_files']) + tmp_dir_paths.append(tmp_dir) + + # Update env vars after creating provider_config_files + # so that we can grab the tmp_dir if it exists + env = account.get('env_vars', {}) + if env and tmp_dir: + for key, value in env.items(): + env[key] = Template(value).substitute(tmpdir=tmp_dir) + name = account['name'] provider = account['provider'] processes[name] = self.get_provider_tags(provider, env) @@ -182,9 +202,9 @@ class MultiEc2(object): }) finally: - # Clean up the mkstemp file - for tmp_file in tmp_file_paths: - os.unlink(tmp_file) + # Clean up the mkdtemp dirs + for tmp_dir in tmp_dir_paths: + shutil.rmtree(tmp_dir) return all_results @@ -223,7 +243,7 @@ class MultiEc2(object): ] raise RuntimeError('\n'.join(err_msg).format(**result)) else: - self.all_ec2_results[result['name']] = json.loads(result['out']) + self.all_inventory_results[result['name']] = json.loads(result['out']) # Check if user wants extra vars in yaml by # having hostvars and all_group defined @@ -231,29 +251,52 @@ class MultiEc2(object): self.apply_account_config(acc_config) # Build results by merging all dictionaries - values = self.all_ec2_results.values() + values = self.all_inventory_results.values() values.insert(0, self.result) for result in values: - MultiEc2.merge_destructively(self.result, result) + MultiInventory.merge_destructively(self.result, result) + + def add_entry(self, data, keys, item): + ''' Add an item to a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + keys = a.b + item = c + ''' + if "." in keys: + key, rest = keys.split(".", 1) + if key not in data: + data[key] = {} + self.add_entry(data[key], rest, item) + else: + data[keys] = item + + def get_entry(self, data, keys): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + keys = a.b + return c + ''' + if keys and "." in keys: + key, rest = keys.split(".", 1) + return self.get_entry(data[key], rest) + else: + return data.get(keys, None) def apply_account_config(self, acc_config): ''' Apply account config settings ''' - results = self.all_ec2_results[acc_config['name']] + results = self.all_inventory_results[acc_config['name']] + results['all_hosts'] = results['_meta']['hostvars'].keys() # Update each hostvar with the newly desired key: value from extra_* - for _extra in ['extra_groups', 'extra_vars']: + for _extra in ['extra_vars', 'extra_groups']: for new_var, value in acc_config.get(_extra, {}).items(): - # Verify the account results look sane - # by checking for these keys ('_meta' and 'hostvars' exist) - if results.has_key('_meta') and results['_meta'].has_key('hostvars'): - for data in results['_meta']['hostvars'].values(): - data[str(new_var)] = str(value) + for data in results['_meta']['hostvars'].values(): + self.add_entry(data, new_var, value) # Add this group - if _extra == 'extra_groups' and results.has_key(acc_config['all_group']): - results["%s_%s" % (new_var, value)] = \ - copy.copy(results[acc_config['all_group']]) + if _extra == 'extra_groups': + results["%s_%s" % (new_var, value)] = copy.copy(results['all_hosts']) # Clone groups goes here for to_name, from_name in acc_config.get('clone_groups', {}).items(): @@ -262,14 +305,11 @@ class MultiEc2(object): # Clone vars goes here for to_name, from_name in acc_config.get('clone_vars', {}).items(): - # Verify the account results look sane - # by checking for these keys ('_meta' and 'hostvars' exist) - if results.has_key('_meta') and results['_meta'].has_key('hostvars'): - for data in results['_meta']['hostvars'].values(): - data[str(to_name)] = data.get(str(from_name), 'nil') + for data in results['_meta']['hostvars'].values(): + self.add_entry(data, to_name, self.get_entry(data, from_name)) - # store the results back into all_ec2_results - self.all_ec2_results[acc_config['name']] = results + # store the results back into all_inventory_results + self.all_inventory_results[acc_config['name']] = results @staticmethod def merge_destructively(input_a, input_b): @@ -277,7 +317,7 @@ class MultiEc2(object): for key in input_b: if key in input_a: if isinstance(input_a[key], dict) and isinstance(input_b[key], dict): - MultiEc2.merge_destructively(input_a[key], input_b[key]) + MultiInventory.merge_destructively(input_a[key], input_b[key]) elif input_a[key] == input_b[key]: pass # same leaf value # both lists so add each element in b to a if it does ! exist @@ -333,7 +373,7 @@ class MultiEc2(object): if exc.errno != errno.EEXIST or not os.path.isdir(path): raise - json_data = MultiEc2.json_format_dict(self.result, True) + json_data = MultiInventory.json_format_dict(self.result, True) with open(self.cache_path, 'w') as cache: try: fcntl.flock(cache, fcntl.LOCK_EX) @@ -369,7 +409,7 @@ class MultiEc2(object): if __name__ == "__main__": - MEC2 = MultiEc2() - MEC2.parse_cli_args() - MEC2.run() - print MEC2.result_str() + MI2 = MultiInventory() + MI2.parse_cli_args() + MI2.run() + print MI2.result_str() diff --git a/inventory/multi_inventory.yaml.example b/inventory/multi_inventory.yaml.example new file mode 100644 index 000000000..0f0788d18 --- /dev/null +++ b/inventory/multi_inventory.yaml.example @@ -0,0 +1,51 @@ +# multi ec2 inventory configs +# +cache_location: ~/.ansible/tmp/multi_inventory.cache + +accounts: + - name: aws1 + provider: aws/ec2.py + provider_files: + - name: ec2.ini + content: |- + [ec2] + regions = all + regions_exclude = us-gov-west-1,cn-north-1 + destination_variable = public_dns_name + route53 = False + cache_path = ~/.ansible/tmp + cache_max_age = 300 + vpc_destination_variable = ip_address + env_vars: + AWS_ACCESS_KEY_ID: XXXXXXXXXXXXXXXXXXXX + AWS_SECRET_ACCESS_KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + EC2_INI_PATH: ${tmpdir}/ec2.ini # we replace ${tmpdir} with the temporary directory that we've created for the provider. + extra_vars: + cloud: aws + account: aws1 + +- name: mygce + extra_vars: + cloud: gce + account: gce1 + env_vars: + GCE_INI_PATH: ${tmpdir}/gce.ini # we replace ${tmpdir} with the temporary directory that we've created for the provider. + provider: gce/gce.py + provider_files: + - name: priv_key.pem + contents: |- + -----BEGIN PRIVATE KEY----- + yourprivatekeydatahere + -----END PRIVATE KEY----- + - name: gce.ini + contents: |- + [gce] + gce_service_account_email_address = <uuid>@developer.gserviceaccount.com + gce_service_account_pem_file_path = ${tmpdir}/priv_key.pem # we replace ${tmpdir} with the temporary directory that we've created for the provider. + gce_project_id = gce-project + zone = us-central1-a + network = default + gce_machine_type = n1-standard-2 + gce_machine_image = rhel7 + +cache_max_age: 600 diff --git a/openshift-ansible.spec b/openshift-ansible.spec index 8d79c80c6..f819624b3 100644 --- a/openshift-ansible.spec +++ b/openshift-ansible.spec @@ -5,7 +5,7 @@ } Name: openshift-ansible -Version: 3.0.6 +Version: 3.0.7 Release: 1%{?dist} Summary: Openshift and Atomic Enterprise Ansible License: ASL 2.0 @@ -47,9 +47,9 @@ cp -pP bin/openshift_ansible/* %{buildroot}%{python_sitelib}/openshift_ansible cp -p bin/ossh_bash_completion %{buildroot}/etc/bash_completion.d cp -p bin/openshift_ansible.conf.example %{buildroot}/etc/openshift_ansible/openshift_ansible.conf # Fix links -rm -f %{buildroot}%{python_sitelib}/openshift_ansible/multi_ec2.py +rm -f %{buildroot}%{python_sitelib}/openshift_ansible/multi_inventory.py rm -f %{buildroot}%{python_sitelib}/openshift_ansible/aws -ln -sf %{_datadir}/ansible/inventory/multi_ec2.py %{buildroot}%{python_sitelib}/openshift_ansible/multi_ec2.py +ln -sf %{_datadir}/ansible/inventory/multi_inventory.py %{buildroot}%{python_sitelib}/openshift_ansible/multi_inventory.py ln -sf %{_datadir}/ansible/inventory/aws %{buildroot}%{python_sitelib}/openshift_ansible/aws # openshift-ansible-docs install @@ -60,8 +60,8 @@ mkdir -p %{buildroot}/etc/ansible mkdir -p %{buildroot}%{_datadir}/ansible/inventory mkdir -p %{buildroot}%{_datadir}/ansible/inventory/aws mkdir -p %{buildroot}%{_datadir}/ansible/inventory/gce -cp -p inventory/multi_ec2.py %{buildroot}%{_datadir}/ansible/inventory -cp -p inventory/multi_ec2.yaml.example %{buildroot}/etc/ansible/multi_ec2.yaml +cp -p inventory/multi_inventory.py %{buildroot}%{_datadir}/ansible/inventory +cp -p inventory/multi_inventory.yaml.example %{buildroot}/etc/ansible/multi_inventory.yaml cp -p inventory/aws/hosts/ec2.py %{buildroot}%{_datadir}/ansible/inventory/aws cp -p inventory/gce/hosts/gce.py %{buildroot}%{_datadir}/ansible/inventory/gce @@ -137,7 +137,7 @@ Ansible Inventories used with the openshift-ansible scripts and playbooks. %files inventory %config(noreplace) /etc/ansible/* %dir %{_datadir}/ansible/inventory -%{_datadir}/ansible/inventory/multi_ec2.py* +%{_datadir}/ansible/inventory/multi_inventory.py* %package inventory-aws Summary: Openshift and Atomic Enterprise Ansible Inventories for AWS @@ -230,6 +230,8 @@ BuildArch: noarch %package -n atomic-openshift-utils Summary: Atomic OpenShift Utilities BuildRequires: python-setuptools +Requires: openshift-ansible-playbooks +Requires: openshift-ansible-roles Requires: ansible Requires: python-click Requires: python-setuptools @@ -247,6 +249,45 @@ Atomic OpenShift Utilities includes %changelog +* Wed Nov 04 2015 Kenny Woodson <kwoodson@redhat.com> 3.0.7-1 +- added the %%util in zabbix (mwoodson@redhat.com) +- atomic-openshift-installer: Correct default playbook directory + (smunilla@redhat.com) +- Support for gce (kwoodson@redhat.com) +- fixed a dumb naming mistake (mwoodson@redhat.com) +- added disk tps checks to zabbix (mwoodson@redhat.com) +- atomic-openshift-installer: Correct inaccurate prompt (smunilla@redhat.com) +- atomic-openshift-installer: Add default openshift-ansible-playbook + (smunilla@redhat.com) +- ooinstall: Add check for nopwd sudo (smunilla@redhat.com) +- ooinstall: Update local install check (smunilla@redhat.com) +- oo-install: Support running on the host to be deployed (smunilla@redhat.com) +- Moving to Openshift Etcd application (mmahut@redhat.com) +- Add all the possible servicenames to openshift_all_hostnames for masters + (sdodson@redhat.com) +- Adding openshift.node.etcd items (mmahut@redhat.com) +- Fix etcd cert generation when etcd_interface is defined (jdetiber@redhat.com) +- get zabbix ready to start tracking status of pcp (jdiaz@redhat.com) +- split inventory into subpackages (tdawson@redhat.com) +- changed the cpu alert to only alert if cpu idle more than 5x. Change alert to + warning (mwoodson@redhat.com) +- Rename install_transactions module to openshift_ansible. + (dgoodwin@redhat.com) +- atomic-openshift-installer: Text improvements (smunilla@redhat.com) +- Add utils subpackage missing dep on openshift-ansible-roles. + (dgoodwin@redhat.com) +- Disable requiretty for only the openshift user (error@ioerror.us) +- Don't require tty to run sudo (error@ioerror.us) +- Attempt to remove the various interfaces left over from an install + (bleanhar@redhat.com) +- Pulling latest gce.py module from ansible (kwoodson@redhat.com) +- Disable OpenShift features if installing Atomic Enterprise + (jdetiber@redhat.com) +- Use default playbooks if available. (dgoodwin@redhat.com) +- Add uninstall subcommand. (dgoodwin@redhat.com) +- Add subcommands to CLI. (dgoodwin@redhat.com) +- Remove images options in oadm command (nakayamakenjiro@gmail.com) + * Fri Oct 30 2015 Kenny Woodson <kwoodson@redhat.com> 3.0.6-1 - Adding python-boto and python-libcloud to openshift-ansible-inventory dependency (kwoodson@redhat.com) diff --git a/playbooks/aws/openshift-cluster/launch.yml b/playbooks/aws/openshift-cluster/launch.yml index 786918929..09bf34666 100644 --- a/playbooks/aws/openshift-cluster/launch.yml +++ b/playbooks/aws/openshift-cluster/launch.yml @@ -11,7 +11,7 @@ msg: Deployment type not supported for aws provider yet when: deployment_type == 'enterprise' - - include: ../../common/openshift-cluster/set_etcd_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_etcd_launch_facts.yml - include: tasks/launch_instances.yml vars: instances: "{{ etcd_names }}" @@ -19,7 +19,7 @@ type: "{{ k8s_type }}" g_sub_host_type: "default" - - include: ../../common/openshift-cluster/set_master_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_master_launch_facts.yml - include: tasks/launch_instances.yml vars: instances: "{{ master_names }}" @@ -27,7 +27,7 @@ type: "{{ k8s_type }}" g_sub_host_type: "default" - - include: ../../common/openshift-cluster/set_node_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_node_launch_facts.yml vars: type: "compute" count: "{{ num_nodes }}" @@ -38,7 +38,7 @@ type: "{{ k8s_type }}" g_sub_host_type: "{{ sub_host_type }}" - - include: ../../common/openshift-cluster/set_node_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_node_launch_facts.yml vars: type: "infra" count: "{{ num_infra }}" diff --git a/playbooks/common/openshift-cluster/config.yml b/playbooks/common/openshift-cluster/config.yml index 4c74f96db..57de7130b 100644 --- a/playbooks/common/openshift-cluster/config.yml +++ b/playbooks/common/openshift-cluster/config.yml @@ -1,68 +1,5 @@ --- -- name: Populate config host groups - hosts: localhost - gather_facts: no - tasks: - - fail: - msg: This playbook rquires g_etcd_group to be set - when: g_etcd_group is not defined - - - fail: - msg: This playbook rquires g_masters_group to be set - when: g_masters_group is not defined - - - fail: - msg: This playbook rquires g_nodes_group to be set - when: g_nodes_group is not defined - - - name: Evaluate oo_etcd_to_config - add_host: - name: "{{ item }}" - groups: oo_etcd_to_config - ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" - ansible_sudo: "{{ g_sudo | default(omit) }}" - with_items: groups[g_etcd_group] | default([]) - - - name: Evaluate oo_masters_to_config - add_host: - name: "{{ item }}" - groups: oo_masters_to_config - ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" - ansible_sudo: "{{ g_sudo | default(omit) }}" - with_items: groups[g_masters_group] | default([]) - - - name: Evaluate oo_nodes_to_config - add_host: - name: "{{ item }}" - groups: oo_nodes_to_config - ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" - ansible_sudo: "{{ g_sudo | default(omit) }}" - with_items: groups[g_nodes_group] | default([]) - - - name: Evaluate oo_nodes_to_config - add_host: - name: "{{ item }}" - groups: oo_nodes_to_config - ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" - ansible_sudo: "{{ g_sudo | default(omit) }}" - with_items: groups[g_masters_group] | default([]) - when: g_nodeonmaster is defined and g_nodeonmaster == true - - - name: Evaluate oo_first_etcd - add_host: - name: "{{ groups[g_etcd_group][0] }}" - groups: oo_first_etcd - ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" - ansible_sudo: "{{ g_sudo | default(omit) }}" - when: g_etcd_group in groups and (groups[g_etcd_group] | length) > 0 - - - name: Evaluate oo_first_master - add_host: - name: "{{ groups[g_masters_group][0] }}" - groups: oo_first_master - ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" - ansible_sudo: "{{ g_sudo | default(omit) }}" - when: g_masters_group in groups and (groups[g_masters_group] | length) > 0 +- include: evaluate_groups.yml - include: ../openshift-etcd/config.yml diff --git a/playbooks/common/openshift-cluster/evaluate_groups.yml b/playbooks/common/openshift-cluster/evaluate_groups.yml new file mode 100644 index 000000000..1919660dd --- /dev/null +++ b/playbooks/common/openshift-cluster/evaluate_groups.yml @@ -0,0 +1,64 @@ +--- +- name: Populate config host groups + hosts: localhost + gather_facts: no + tasks: + - fail: + msg: This playbook rquires g_etcd_group to be set + when: g_etcd_group is not defined + + - fail: + msg: This playbook rquires g_masters_group to be set + when: g_masters_group is not defined + + - fail: + msg: This playbook rquires g_nodes_group to be set + when: g_nodes_group is not defined + + - name: Evaluate oo_etcd_to_config + add_host: + name: "{{ item }}" + groups: oo_etcd_to_config + ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" + ansible_sudo: "{{ g_sudo | default(omit) }}" + with_items: groups[g_etcd_group] | default([]) + + - name: Evaluate oo_masters_to_config + add_host: + name: "{{ item }}" + groups: oo_masters_to_config + ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" + ansible_sudo: "{{ g_sudo | default(omit) }}" + with_items: groups[g_masters_group] | default([]) + + - name: Evaluate oo_nodes_to_config + add_host: + name: "{{ item }}" + groups: oo_nodes_to_config + ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" + ansible_sudo: "{{ g_sudo | default(omit) }}" + with_items: groups[g_nodes_group] | default([]) + + - name: Evaluate oo_nodes_to_config + add_host: + name: "{{ item }}" + groups: oo_nodes_to_config + ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" + ansible_sudo: "{{ g_sudo | default(omit) }}" + with_items: groups[g_masters_group] | default([]) + when: g_nodeonmaster is defined and g_nodeonmaster == true + + - name: Evaluate oo_first_etcd + add_host: + name: "{{ groups[g_etcd_group][0] }}" + groups: oo_first_etcd + ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" + when: g_etcd_group in groups and (groups[g_etcd_group] | length) > 0 + + - name: Evaluate oo_first_master + add_host: + name: "{{ groups[g_masters_group][0] }}" + groups: oo_first_master + ansible_ssh_user: "{{ g_ssh_user | default(omit) }}" + ansible_sudo: "{{ g_sudo | default(omit) }}" + when: g_masters_group in groups and (groups[g_masters_group] | length) > 0 diff --git a/playbooks/common/openshift-cluster/scaleup.yml b/playbooks/common/openshift-cluster/scaleup.yml new file mode 100644 index 000000000..201320de8 --- /dev/null +++ b/playbooks/common/openshift-cluster/scaleup.yml @@ -0,0 +1,7 @@ +--- +- include: evaluate_groups.yml + +- include: ../openshift-node/config.yml + vars: + osn_cluster_dns_domain: "{{ hostvars[groups.oo_first_master.0].openshift.dns.domain }}" + osn_cluster_dns_ip: "{{ hostvars[groups.oo_first_master.0].openshift.dns.ip }}" diff --git a/playbooks/common/openshift-cluster/set_etcd_launch_facts_tasks.yml b/playbooks/common/openshift-cluster/tasks/set_etcd_launch_facts.yml index 1a6580795..1a6580795 100644 --- a/playbooks/common/openshift-cluster/set_etcd_launch_facts_tasks.yml +++ b/playbooks/common/openshift-cluster/tasks/set_etcd_launch_facts.yml diff --git a/playbooks/common/openshift-cluster/set_master_launch_facts_tasks.yml b/playbooks/common/openshift-cluster/tasks/set_master_launch_facts.yml index 36d7b7870..36d7b7870 100644 --- a/playbooks/common/openshift-cluster/set_master_launch_facts_tasks.yml +++ b/playbooks/common/openshift-cluster/tasks/set_master_launch_facts.yml diff --git a/playbooks/common/openshift-cluster/set_node_launch_facts_tasks.yml b/playbooks/common/openshift-cluster/tasks/set_node_launch_facts.yml index 278942f8b..278942f8b 100644 --- a/playbooks/common/openshift-cluster/set_node_launch_facts_tasks.yml +++ b/playbooks/common/openshift-cluster/tasks/set_node_launch_facts.yml diff --git a/playbooks/common/openshift-master/config.yml b/playbooks/common/openshift-master/config.yml index 1dec923fc..59c4b2370 100644 --- a/playbooks/common/openshift-master/config.yml +++ b/playbooks/common/openshift-master/config.yml @@ -199,9 +199,18 @@ validate_checksum: yes with_items: masters_needing_certs +- name: Inspect named certificates + hosts: oo_first_master + tasks: + - name: Collect certificate names + set_fact: + parsed_named_certificates: "{{ openshift_master_named_certificates | oo_parse_certificate_names(master_cert_config_dir, openshift.common.internal_hostnames) }}" + when: openshift_master_named_certificates is defined + - name: Configure master instances hosts: oo_masters_to_config vars: + named_certificates: "{{ hostvars[groups['oo_first_master'][0]]['parsed_named_certificates'] | default([])}}" sync_tmpdir: "{{ hostvars.localhost.g_master_mktemp.stdout }}" openshift_master_ha: "{{ groups.oo_masters_to_config | length > 1 }}" embedded_etcd: "{{ openshift.master.embedded_etcd }}" diff --git a/playbooks/gce/openshift-cluster/launch.yml b/playbooks/gce/openshift-cluster/launch.yml index c22b897d5..8be5d53e7 100644 --- a/playbooks/gce/openshift-cluster/launch.yml +++ b/playbooks/gce/openshift-cluster/launch.yml @@ -9,7 +9,7 @@ - fail: msg="Deployment type not supported for gce provider yet" when: deployment_type == 'enterprise' - - include: ../../common/openshift-cluster/set_master_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_master_launch_facts.yml - include: tasks/launch_instances.yml vars: instances: "{{ master_names }}" @@ -17,7 +17,7 @@ type: "{{ k8s_type }}" g_sub_host_type: "default" - - include: ../../common/openshift-cluster/set_node_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_node_launch_facts.yml vars: type: "compute" count: "{{ num_nodes }}" @@ -28,7 +28,7 @@ type: "{{ k8s_type }}" g_sub_host_type: "{{ sub_host_type }}" - - include: ../../common/openshift-cluster/set_node_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_node_launch_facts.yml vars: type: "infra" count: "{{ num_infra }}" diff --git a/playbooks/libvirt/openshift-cluster/launch.yml b/playbooks/libvirt/openshift-cluster/launch.yml index d3e768de5..8d7949dd1 100644 --- a/playbooks/libvirt/openshift-cluster/launch.yml +++ b/playbooks/libvirt/openshift-cluster/launch.yml @@ -17,7 +17,7 @@ - include: tasks/configure_libvirt.yml - - include: ../../common/openshift-cluster/set_etcd_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_etcd_launch_facts.yml - include: tasks/launch_instances.yml vars: instances: "{{ etcd_names }}" @@ -25,7 +25,7 @@ type: "{{ k8s_type }}" g_sub_host_type: "default" - - include: ../../common/openshift-cluster/set_master_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_master_launch_facts.yml - include: tasks/launch_instances.yml vars: instances: "{{ master_names }}" @@ -33,7 +33,7 @@ type: "{{ k8s_type }}" g_sub_host_type: "default" - - include: ../../common/openshift-cluster/set_node_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_node_launch_facts.yml vars: type: "compute" count: "{{ num_nodes }}" @@ -44,7 +44,7 @@ type: "{{ k8s_type }}" g_sub_host_type: "{{ sub_host_type }}" - - include: ../../common/openshift-cluster/set_node_launch_facts_tasks.yml + - include: ../../common/openshift-cluster/tasks/set_node_launch_facts.yml vars: type: "infra" count: "{{ num_infra }}" diff --git a/playbooks/libvirt/openshift-cluster/templates/user-data b/playbooks/libvirt/openshift-cluster/templates/user-data index eacae7c7e..e0c966e45 100644 --- a/playbooks/libvirt/openshift-cluster/templates/user-data +++ b/playbooks/libvirt/openshift-cluster/templates/user-data @@ -19,5 +19,11 @@ system_info: ssh_authorized_keys: - {{ lookup('file', '~/.ssh/id_rsa.pub') }} +write_files: + - path: /etc/sudoers.d/00-openshift-no-requiretty + permissions: 440 + content: | + Defaults:openshift !requiretty + runcmd: - NETWORK_CONFIG=/etc/sysconfig/network-scripts/ifcfg-eth0; if ! grep DHCP_HOSTNAME ${NETWORK_CONFIG}; then echo 'DHCP_HOSTNAME="{{ item[0] }}.example.com"' >> ${NETWORK_CONFIG}; fi; pkill -9 dhclient; service network restart diff --git a/roles/etcd/README.md b/roles/etcd/README.md index 49207c428..88e4ff874 100644 --- a/roles/etcd/README.md +++ b/roles/etcd/README.md @@ -17,7 +17,7 @@ TODO Dependencies ------------ -None +etcd-common Example Playbook ---------------- diff --git a/roles/etcd/defaults/main.yaml b/roles/etcd/defaults/main.yaml index 0f216b84e..0fd3de585 100644 --- a/roles/etcd/defaults/main.yaml +++ b/roles/etcd/defaults/main.yaml @@ -2,16 +2,8 @@ etcd_interface: "{{ ansible_default_ipv4.interface }}" etcd_client_port: 2379 etcd_peer_port: 2380 -etcd_peers_group: etcd etcd_url_scheme: http etcd_peer_url_scheme: http -etcd_conf_dir: /etc/etcd -etcd_ca_file: "{{ etcd_conf_dir }}/ca.crt" -etcd_cert_file: "{{ etcd_conf_dir }}/server.crt" -etcd_key_file: "{{ etcd_conf_dir }}/server.key" -etcd_peer_ca_file: "{{ etcd_conf_dir }}/ca.crt" -etcd_peer_cert_file: "{{ etcd_conf_dir }}/peer.crt" -etcd_peer_key_file: "{{ etcd_conf_dir }}/peer.key" etcd_initial_cluster_state: new etcd_initial_cluster_token: etcd-cluster-1 diff --git a/roles/etcd/handlers/main.yml b/roles/etcd/handlers/main.yml index b897913f9..4c0efb97b 100644 --- a/roles/etcd/handlers/main.yml +++ b/roles/etcd/handlers/main.yml @@ -1,3 +1,4 @@ --- - name: restart etcd service: name=etcd state=restarted + when: not etcd_service_status_changed | default(false) diff --git a/roles/etcd/meta/main.yml b/roles/etcd/meta/main.yml index 92d44ef4d..a71b36237 100644 --- a/roles/etcd/meta/main.yml +++ b/roles/etcd/meta/main.yml @@ -17,4 +17,4 @@ galaxy_info: - system dependencies: - { role: os_firewall } -- { role: openshift_repos } +- { role: etcd_common } diff --git a/roles/etcd/tasks/main.yml b/roles/etcd/tasks/main.yml index 656901409..fcbdecd37 100644 --- a/roles/etcd/tasks/main.yml +++ b/roles/etcd/tasks/main.yml @@ -1,4 +1,12 @@ --- +- fail: + msg: Interface {{ etcd_interface }} not found + when: "'ansible_' ~ etcd_interface not in hostvars[inventory_hostname]" + +- fail: + msg: IPv4 address not found for {{ etcd_interface }} + when: "'ipv4' not in hostvars[inventory_hostname]['ansible_' ~ etcd_interface] or 'address' not in hostvars[inventory_hostname]['ansible_' ~ etcd_interface].ipv4" + - name: Install etcd yum: pkg=etcd-2.* state=present @@ -49,5 +57,5 @@ enabled: yes register: start_result -- pause: seconds=30 - when: start_result | changed +- set_fact: + etcd_service_status_changed = start_result | changed diff --git a/roles/etcd/templates/etcd.conf.j2 b/roles/etcd/templates/etcd.conf.j2 index 9ac23b1dd..32577c96c 100644 --- a/roles/etcd/templates/etcd.conf.j2 +++ b/roles/etcd/templates/etcd.conf.j2 @@ -1,9 +1,9 @@ {% macro initial_cluster() -%} {% for host in groups[etcd_peers_group] -%} {% if loop.last -%} -{{ host }}={{ etcd_peer_url_scheme }}://{{ hostvars[host]['ansible_' + etcd_interface]['ipv4']['address'] }}:{{ etcd_peer_port }} +{{ host }}={{ etcd_peer_url_scheme }}://{{ etcd_host_int_map[host].interface.ipv4.address }}:{{ etcd_peer_port }} {%- else -%} -{{ host }}={{ etcd_peer_url_scheme }}://{{ hostvars[host]['ansible_' + etcd_interface]['ipv4']['address'] }}:{{ etcd_peer_port }}, +{{ host }}={{ etcd_peer_url_scheme }}://{{ etcd_host_int_map[host].interface.ipv4.address }}:{{ etcd_peer_port }}, {%- endif -%} {% endfor -%} {% endmacro -%} diff --git a/roles/etcd_ca/meta/main.yml b/roles/etcd_ca/meta/main.yml index fb9280c9e..d02456ca3 100644 --- a/roles/etcd_ca/meta/main.yml +++ b/roles/etcd_ca/meta/main.yml @@ -13,4 +13,4 @@ galaxy_info: - cloud - system dependencies: -- { role: openshift_repos } +- { role: etcd_common } diff --git a/roles/etcd_ca/tasks/main.yml b/roles/etcd_ca/tasks/main.yml index 625756867..d32f5e48c 100644 --- a/roles/etcd_ca/tasks/main.yml +++ b/roles/etcd_ca/tasks/main.yml @@ -1,14 +1,14 @@ --- - file: - path: "{{ etcd_ca_dir }}/{{ item }}" + path: "{{ item }}" state: directory mode: 0700 owner: root group: root with_items: - - certs - - crl - - fragments + - "{{ etcd_ca_new_certs_dir }}" + - "{{ etcd_ca_crl_dir }}" + - "{{ etcd_ca_dir }}/fragments" - command: cp /etc/pki/tls/openssl.cnf ./ args: @@ -22,25 +22,25 @@ - assemble: src: "{{ etcd_ca_dir }}/fragments" - dest: "{{ etcd_ca_dir }}/openssl.cnf" + dest: "{{ etcd_openssl_conf }}" -- command: touch index.txt +- command: touch {{ etcd_ca_db }} args: - chdir: "{{ etcd_ca_dir }}" - creates: "{{ etcd_ca_dir }}/index.txt" + creates: "{{ etcd_ca_db }}" - copy: - dest: "{{ etcd_ca_dir }}/serial" + dest: "{{ etcd_ca_serial }}" content: "01" force: no - command: > - openssl req -config openssl.cnf -newkey rsa:4096 - -keyout ca.key -new -out ca.crt -x509 -extensions etcd_v3_ca_self - -batch -nodes -subj /CN=etcd-signer@{{ ansible_date_time.epoch }} - -days 365 + openssl req -config {{ etcd_openssl_conf }} -newkey rsa:4096 + -keyout {{ etcd_ca_key }} -new -out {{ etcd_ca_cert }} + -x509 -extensions {{ etcd_ca_exts_self }} -batch -nodes + -days {{ etcd_ca_default_days }} + -subj /CN=etcd-signer@{{ ansible_date_time.epoch }} args: chdir: "{{ etcd_ca_dir }}" - creates: "{{ etcd_ca_dir }}/ca.crt" + creates: "{{ etcd_ca_cert }}" environment: - SAN: '' + SAN: 'etcd-signer' diff --git a/roles/etcd_ca/templates/openssl_append.j2 b/roles/etcd_ca/templates/openssl_append.j2 index de2adaead..f28316fc2 100644 --- a/roles/etcd_ca/templates/openssl_append.j2 +++ b/roles/etcd_ca/templates/openssl_append.j2 @@ -1,20 +1,20 @@ -[ etcd_v3_req ] +[ {{ etcd_req_ext }} ] basicConstraints = critical,CA:FALSE keyUsage = digitalSignature,keyEncipherment subjectAltName = ${ENV::SAN} -[ etcd_ca ] +[ {{ etcd_ca_name }} ] dir = {{ etcd_ca_dir }} -crl_dir = $dir/crl -database = $dir/index.txt -new_certs_dir = $dir/certs -certificate = $dir/ca.crt -serial = $dir/serial -private_key = $dir/ca.key -crl_number = $dir/crlnumber -x509_extensions = etcd_v3_ca_client -default_days = 365 +crl_dir = {{ etcd_ca_crl_dir }} +database = {{ etcd_ca_db }} +new_certs_dir = {{ etcd_ca_new_certs_dir }} +certificate = {{ etcd_ca_cert }} +serial = {{ etcd_ca_serial }} +private_key = {{ etcd_ca_key }} +crl_number = {{ etcd_ca_crl_number }} +x509_extensions = {{ etcd_ca_exts_client }} +default_days = {{ etcd_ca_default_days }} default_md = sha256 preserve = no name_opt = ca_default @@ -23,27 +23,27 @@ policy = policy_anything unique_subject = no copy_extensions = copy -[ etcd_v3_ca_self ] +[ {{ etcd_ca_exts_self }} ] authorityKeyIdentifier = keyid,issuer basicConstraints = critical,CA:TRUE,pathlen:0 keyUsage = critical,digitalSignature,keyEncipherment,keyCertSign subjectKeyIdentifier = hash -[ etcd_v3_ca_peer ] +[ {{ etcd_ca_exts_peer }} ] authorityKeyIdentifier = keyid,issuer:always basicConstraints = critical,CA:FALSE extendedKeyUsage = clientAuth,serverAuth keyUsage = digitalSignature,keyEncipherment subjectKeyIdentifier = hash -[ etcd_v3_ca_server ] +[ {{ etcd_ca_exts_server }} ] authorityKeyIdentifier = keyid,issuer:always basicConstraints = critical,CA:FALSE extendedKeyUsage = serverAuth keyUsage = digitalSignature,keyEncipherment subjectKeyIdentifier = hash -[ etcd_v3_ca_client ] +[ {{ etcd_ca_exts_client }} ] authorityKeyIdentifier = keyid,issuer:always basicConstraints = critical,CA:FALSE extendedKeyUsage = clientAuth diff --git a/roles/etcd_ca/vars/main.yml b/roles/etcd_ca/vars/main.yml deleted file mode 100644 index 901e95027..000000000 --- a/roles/etcd_ca/vars/main.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -etcd_conf_dir: /etc/etcd -etcd_ca_dir: /etc/etcd/ca diff --git a/roles/etcd_certificates/tasks/client.yml b/roles/etcd_certificates/tasks/client.yml index 28f33f442..6aa4883e0 100644 --- a/roles/etcd_certificates/tasks/client.yml +++ b/roles/etcd_certificates/tasks/client.yml @@ -32,7 +32,7 @@ creates: "{{ etcd_generated_certs_dir ~ '/' ~ item.etcd_cert_subdir ~ '/' ~ item.etcd_cert_prefix ~ 'client.crt' }}" environment: - SAN: '' + SAN: "IP:{{ item.openshift.common.ip }}" with_items: etcd_needing_client_certs - file: diff --git a/roles/etcd_certificates/tasks/main.yml b/roles/etcd_certificates/tasks/main.yml index da875e8ea..3bb715943 100644 --- a/roles/etcd_certificates/tasks/main.yml +++ b/roles/etcd_certificates/tasks/main.yml @@ -4,6 +4,3 @@ - include: server.yml when: etcd_needing_server_certs is defined and etcd_needing_server_certs - - - diff --git a/roles/etcd_certificates/tasks/server.yml b/roles/etcd_certificates/tasks/server.yml index 727b7fa2c..3499dcbef 100644 --- a/roles/etcd_certificates/tasks/server.yml +++ b/roles/etcd_certificates/tasks/server.yml @@ -18,7 +18,7 @@ creates: "{{ etcd_generated_certs_dir ~ '/' ~ item.etcd_cert_subdir ~ '/' ~ item.etcd_cert_prefix ~ 'server.csr' }}" environment: - SAN: "IP:{{ item.openshift.common.ip }}" + SAN: "IP:{{ etcd_host_int_map[item.inventory_hostname].interface.ipv4.address }}" with_items: etcd_needing_server_certs - name: Sign and create the server crt @@ -32,7 +32,7 @@ creates: "{{ etcd_generated_certs_dir ~ '/' ~ item.etcd_cert_subdir ~ '/' ~ item.etcd_cert_prefix ~ 'server.crt' }}" environment: - SAN: '' + SAN: "IP:{{ etcd_host_int_map[item.inventory_hostname].interface.ipv4.address }}" with_items: etcd_needing_server_certs - name: Create the peer csr @@ -47,7 +47,7 @@ creates: "{{ etcd_generated_certs_dir ~ '/' ~ item.etcd_cert_subdir ~ '/' ~ item.etcd_cert_prefix ~ 'peer.csr' }}" environment: - SAN: "IP:{{ item.openshift.common.ip }}" + SAN: "IP:{{ etcd_host_int_map[item.inventory_hostname].interface.ipv4.address }}" with_items: etcd_needing_server_certs - name: Sign and create the peer crt @@ -61,7 +61,7 @@ creates: "{{ etcd_generated_certs_dir ~ '/' ~ item.etcd_cert_subdir ~ '/' ~ item.etcd_cert_prefix ~ 'peer.crt' }}" environment: - SAN: '' + SAN: "IP:{{ etcd_host_int_map[item.inventory_hostname].interface.ipv4.address }}" with_items: etcd_needing_server_certs - file: @@ -69,5 +69,3 @@ dest: "{{ etcd_generated_certs_dir}}/{{ item.etcd_cert_subdir }}/{{ item.etcd_cert_prefix }}ca.crt" state: hard with_items: etcd_needing_server_certs - - diff --git a/roles/etcd_certificates/vars/main.yml b/roles/etcd_certificates/vars/main.yml deleted file mode 100644 index 0eaeeb82b..000000000 --- a/roles/etcd_certificates/vars/main.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -etcd_conf_dir: /etc/etcd -etcd_ca_dir: /etc/etcd/ca -etcd_generated_certs_dir: /etc/etcd/generated_certs -etcd_ca_cert: "{{ etcd_ca_dir }}/ca.crt" -etcd_ca_key: "{{ etcd_ca_dir }}/ca.key" -etcd_openssl_conf: "{{ etcd_ca_dir }}/openssl.cnf" -etcd_ca_name: etcd_ca -etcd_req_ext: etcd_v3_req -etcd_ca_exts_peer: etcd_v3_ca_peer -etcd_ca_exts_server: etcd_v3_ca_server diff --git a/roles/etcd_common/README.md b/roles/etcd_common/README.md new file mode 100644 index 000000000..131a01490 --- /dev/null +++ b/roles/etcd_common/README.md @@ -0,0 +1,34 @@ +etcd_common +======================== + +TODO + +Requirements +------------ + +TODO + +Role Variables +-------------- + +TODO + +Dependencies +------------ + +openshift-repos + +Example Playbook +---------------- + +TODO + +License +------- + +Apache License Version 2.0 + +Author Information +------------------ + +Jason DeTiberus (jdetiber@redhat.com) diff --git a/roles/etcd_common/defaults/main.yml b/roles/etcd_common/defaults/main.yml new file mode 100644 index 000000000..96f4b63af --- /dev/null +++ b/roles/etcd_common/defaults/main.yml @@ -0,0 +1,30 @@ +--- +etcd_peers_group: etcd + +# etcd server vars +etcd_conf_dir: /etc/etcd +etcd_ca_file: "{{ etcd_conf_dir }}/ca.crt" +etcd_cert_file: "{{ etcd_conf_dir }}/server.crt" +etcd_key_file: "{{ etcd_conf_dir }}/server.key" +etcd_peer_ca_file: "{{ etcd_conf_dir }}/ca.crt" +etcd_peer_cert_file: "{{ etcd_conf_dir }}/peer.crt" +etcd_peer_key_file: "{{ etcd_conf_dir }}/peer.key" + +# etcd ca vars +etcd_ca_dir: "{{ etcd_conf_dir}}/ca" +etcd_generated_certs_dir: "{{ etcd_conf_dir }}/generated_certs" +etcd_ca_cert: "{{ etcd_ca_dir }}/ca.crt" +etcd_ca_key: "{{ etcd_ca_dir }}/ca.key" +etcd_openssl_conf: "{{ etcd_ca_dir }}/openssl.cnf" +etcd_ca_name: etcd_ca +etcd_req_ext: etcd_v3_req +etcd_ca_exts_peer: etcd_v3_ca_peer +etcd_ca_exts_server: etcd_v3_ca_server +etcd_ca_exts_self: etcd_v3_ca_self +etcd_ca_exts_client: etcd_v3_ca_client +etcd_ca_crl_dir: "{{ etcd_ca_dir }}/crl" +etcd_ca_new_certs_dir: "{{ etcd_ca_dir }}/certs" +etcd_ca_db: "{{ etcd_ca_dir }}/index.txt" +etcd_ca_serial: "{{ etcd_ca_dir }}/serial" +etcd_ca_crl_number: "{{ etcd_ca_dir }}/crlnumber" +etcd_ca_default_days: 365 diff --git a/roles/etcd_common/meta/main.yml b/roles/etcd_common/meta/main.yml new file mode 100644 index 000000000..fb9280c9e --- /dev/null +++ b/roles/etcd_common/meta/main.yml @@ -0,0 +1,16 @@ +--- +galaxy_info: + author: Jason DeTiberus + description: + company: Red Hat, Inc. + license: Apache License, Version 2.0 + min_ansible_version: 1.9 + platforms: + - name: EL + versions: + - 7 + categories: + - cloud + - system +dependencies: +- { role: openshift_repos } diff --git a/roles/etcd_common/tasks/main.yml b/roles/etcd_common/tasks/main.yml new file mode 100644 index 000000000..cd108495d --- /dev/null +++ b/roles/etcd_common/tasks/main.yml @@ -0,0 +1,13 @@ +--- +- set_fact: + etcd_host_int_map: "{{ lookup('template', '../templates/host_int_map.j2') | from_yaml }}" + +- fail: + msg: "Interface {{ item.value.etcd_interface }} not found on host {{ item.key }}" + when: "'etcd_interface' in item.value and 'interface' not in item.value" + with_dict: etcd_host_int_map + +- fail: + msg: IPv4 address not found for {{ item.value.interface.device }} on host {{ item.key }} + when: "'ipv4' not in item.value.interface or 'address' not in item.value.interface.ipv4" + with_dict: etcd_host_int_map diff --git a/roles/etcd_common/templates/host_int_map.j2 b/roles/etcd_common/templates/host_int_map.j2 new file mode 100644 index 000000000..9c9c76413 --- /dev/null +++ b/roles/etcd_common/templates/host_int_map.j2 @@ -0,0 +1,13 @@ +--- +{% for host in groups[etcd_peers_group] %} +{% set entry=hostvars[host] %} +{{ entry.inventory_hostname }}: +{% if 'etcd_interface' in entry %} + etcd_interface: {{ entry.etcd_interface }} +{% if entry.etcd_interface in entry.ansible_interfaces %} + interface: {{ entry['ansible_' ~ entry.etcd_interface] | to_json }} +{% endif %} +{% else %} + interface: {{ entry['ansible_' ~ entry.ansible_default_ipv4.interface] | to_json }} +{% endif %} +{% endfor %} diff --git a/roles/openshift_ansible_inventory/tasks/main.yml b/roles/openshift_ansible_inventory/tasks/main.yml index 9cc15c0a8..f6919dada 100644 --- a/roles/openshift_ansible_inventory/tasks/main.yml +++ b/roles/openshift_ansible_inventory/tasks/main.yml @@ -1,12 +1,16 @@ --- - yum: - name: openshift-ansible-inventory + name: "{{ item }}" state: present + with_items: + - openshift-ansible-inventory + - openshift-ansible-inventory-aws + - openshift-ansible-inventory-gce - name: copy: content: "{{ oo_inventory_accounts | to_nice_yaml }}" - dest: /etc/ansible/multi_ec2.yaml + dest: /etc/ansible/multi_inventory.yaml group: "{{ oo_inventory_group }}" owner: "{{ oo_inventory_owner }}" mode: "0640" @@ -20,17 +24,17 @@ - file: state: link - src: /usr/share/ansible/inventory/multi_ec2.py - dest: /etc/ansible/inventory/multi_ec2.py + src: /usr/share/ansible/inventory/multi_inventory.py + dest: /etc/ansible/inventory/multi_inventory.py owner: root group: libra_ops # This cron uses the above location to call its job - name: Cron to keep cache fresh cron: - name: 'multi_ec2_inventory' + name: 'multi_inventory' minute: '*/10' - job: '/usr/share/ansible/inventory/multi_ec2.py --refresh-cache &> /dev/null' + job: '/usr/share/ansible/inventory/multi_inventory.py --refresh-cache &> /dev/null' when: oo_cron_refresh_cache is defined and oo_cron_refresh_cache - name: Set cache location diff --git a/roles/openshift_facts/library/openshift_facts.py b/roles/openshift_facts/library/openshift_facts.py index ac19fac5f..28866bd48 100755 --- a/roles/openshift_facts/library/openshift_facts.py +++ b/roles/openshift_facts/library/openshift_facts.py @@ -22,6 +22,7 @@ import copy import os from distutils.util import strtobool from distutils.version import LooseVersion +from netaddr import IPNetwork def hostname_valid(hostname): @@ -483,17 +484,34 @@ def set_aggregate_facts(facts): dict: the facts dict updated with aggregated facts """ all_hostnames = set() + internal_hostnames = set() if 'common' in facts: all_hostnames.add(facts['common']['hostname']) all_hostnames.add(facts['common']['public_hostname']) + all_hostnames.add(facts['common']['ip']) + all_hostnames.add(facts['common']['public_ip']) + + internal_hostnames.add(facts['common']['hostname']) + internal_hostnames.add(facts['common']['ip']) if 'master' in facts: + # FIXME: not sure why but facts['dns']['domain'] fails + cluster_domain = 'cluster.local' if 'cluster_hostname' in facts['master']: all_hostnames.add(facts['master']['cluster_hostname']) if 'cluster_public_hostname' in facts['master']: all_hostnames.add(facts['master']['cluster_public_hostname']) + svc_names = ['openshift', 'openshift.default', 'openshift.default.svc', + 'openshift.default.svc.' + cluster_domain, 'kubernetes', 'kubernetes.default', + 'kubernetes.default.svc', 'kubernetes.default.svc.' + cluster_domain] + all_hostnames.update(svc_names) + internal_hostnames.update(svc_names) + first_svc_ip = str(IPNetwork(facts['master']['portal_net'])[1]) + all_hostnames.add(first_svc_ip) + internal_hostnames.add(first_svc_ip) facts['common']['all_hostnames'] = list(all_hostnames) + facts['common']['internal_hostnames'] = list(all_hostnames) return facts @@ -508,8 +526,9 @@ def set_deployment_facts_if_unset(facts): dict: the facts dict updated with the generated deployment_type facts """ - # Perhaps re-factor this as a map? - # pylint: disable=too-many-branches + # disabled to avoid breaking up facts related to deployment type into + # multiple methods for now. + # pylint: disable=too-many-statements, too-many-branches if 'common' in facts: deployment_type = facts['common']['deployment_type'] if 'service_type' not in facts['common']: @@ -550,6 +569,17 @@ def set_deployment_facts_if_unset(facts): registry_url = 'aep3/aep-${component}:${version}' facts[role]['registry_url'] = registry_url + if 'master' in facts: + deployment_type = facts['common']['deployment_type'] + openshift_features = ['Builder', 'S2IBuilder', 'WebConsole'] + if 'disabled_features' in facts['master']: + if deployment_type == 'atomic-enterprise': + curr_disabled_features = set(facts['master']['disabled_features']) + facts['master']['disabled_features'] = list(curr_disabled_features.union(openshift_features)) + else: + if deployment_type == 'atomic-enterprise': + facts['master']['disabled_features'] = openshift_features + if 'node' in facts: deployment_type = facts['common']['deployment_type'] if 'storage_plugin_deps' not in facts['node']: diff --git a/roles/openshift_facts/tasks/main.yml b/roles/openshift_facts/tasks/main.yml index 6301d4fc0..a46b45b8c 100644 --- a/roles/openshift_facts/tasks/main.yml +++ b/roles/openshift_facts/tasks/main.yml @@ -6,5 +6,8 @@ - ansible_version | version_compare('1.9.0', 'ne') - ansible_version | version_compare('1.9.0.1', 'ne') +- name: Ensure python-netaddr is installed + yum: pkg=python-netaddr state=installed + - name: Gather Cluster facts openshift_facts: diff --git a/roles/openshift_master/tasks/main.yml b/roles/openshift_master/tasks/main.yml index 94eb73346..3a886935f 100644 --- a/roles/openshift_master/tasks/main.yml +++ b/roles/openshift_master/tasks/main.yml @@ -62,6 +62,7 @@ api_server_args: "{{ osm_api_server_args | default(None) }}" controller_args: "{{ osm_controller_args | default(None) }}" infra_nodes: "{{ num_infra | default(None) }}" + disabled_features: "{{ osm_disabled_features | default(None) }}" - name: Install Master package yum: pkg={{ openshift.common.service_type }}-master{{ openshift_version }} state=present diff --git a/roles/openshift_master/templates/master.yaml.v1.j2 b/roles/openshift_master/templates/master.yaml.v1.j2 index 527c5231a..9547a6945 100644 --- a/roles/openshift_master/templates/master.yaml.v1.j2 +++ b/roles/openshift_master/templates/master.yaml.v1.j2 @@ -16,12 +16,18 @@ assetConfig: maxRequestsInFlight: 0 requestTimeoutSeconds: 0 corsAllowedOrigins: -{% for origin in ['127.0.0.1', 'localhost', openshift.common.hostname, openshift.common.ip, openshift.common.public_hostname, openshift.common.public_ip] %} +{% for origin in ['127.0.0.1', 'localhost', openshift.common.hostname, openshift.common.ip, openshift.common.public_hostname, openshift.common.public_ip] | unique %} - {{ origin }} {% endfor %} {% for custom_origin in openshift.master.custom_cors_origins | default("") %} - {{ custom_origin }} {% endfor %} +{% for name in (named_certificates | map(attribute='names')) | list | oo_flatten %} + - {{ name }} +{% endfor %} +{% if 'disabled_features' in openshift.master %} +disabledFeatures: {{ openshift.master.disabled_features | to_json }} +{% endif %} {% if openshift.master.embedded_dns | bool %} dnsConfig: bindAddress: {{ openshift.master.bind_addr }}:{{ openshift.master.dns_port }} @@ -130,3 +136,14 @@ servingInfo: keyFile: master.server.key maxRequestsInFlight: 500 requestTimeoutSeconds: 3600 +{% if named_certificates %} + namedCertificates: +{% for named_certificate in named_certificates %} + - certFile: {{ named_certificate['certfile'] }} + keyFile: {{ named_certificate['keyfile'] }} + names: +{% for name in named_certificate['names'] %} + - "{{ name }}" +{% endfor %} +{% endfor %} +{% endif %} diff --git a/roles/openshift_master_certificates/tasks/main.yml b/roles/openshift_master_certificates/tasks/main.yml index e4602337e..4b39b043a 100644 --- a/roles/openshift_master_certificates/tasks/main.yml +++ b/roles/openshift_master_certificates/tasks/main.yml @@ -34,6 +34,8 @@ - serviceaccounts.private.key - serviceaccounts.public.key +- debug: msg="{{ item.openshift.master.all_hostnames | join (',') }}" + with_items: masters_needing_certs - name: Create the master certificates if they do not already exist command: > diff --git a/roles/os_zabbix/tasks/main.yml b/roles/os_zabbix/tasks/main.yml index a503b24d7..82bf78b57 100644 --- a/roles/os_zabbix/tasks/main.yml +++ b/roles/os_zabbix/tasks/main.yml @@ -15,6 +15,7 @@ - include_vars: template_ops_tools.yml - include_vars: template_app_zabbix_server.yml - include_vars: template_app_zabbix_agent.yml +- include_vars: template_performance_copilot.yml - name: Include Template Heartbeat include: ../../lib_zabbix/tasks/create_template.yml @@ -79,3 +80,11 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + +- name: Include Template Performance Copilot + include: ../../lib_zabbix/tasks/create_template.yml + vars: + template: "{{ g_template_performance_copilot }}" + server: "{{ ozb_server }}" + user: "{{ ozb_user }}" + password: "{{ ozb_password }}" diff --git a/roles/os_zabbix/vars/template_openshift_master.yml b/roles/os_zabbix/vars/template_openshift_master.yml index 1de4fefbb..6defc4989 100644 --- a/roles/os_zabbix/vars/template_openshift_master.yml +++ b/roles/os_zabbix/vars/template_openshift_master.yml @@ -31,6 +31,78 @@ g_template_openshift_master: applications: - Openshift Master + - key: openshift.master.etcd.create.success + description: Show number of successful create actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.create.fail + description: Show number of failed create actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.delete.success + description: Show number of successful delete actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.delete.fail + description: Show number of failed delete actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.get.success + description: Show number of successful get actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.get.fail + description: Show number of failed get actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.set.success + description: Show number of successful set actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.set.fail + description: Show number of failed set actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.update.success + description: Show number of successful update actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.update.fail + description: Show number of failed update actions + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.watchers + description: Show number of etcd watchers + type: int + applications: + - Openshift Etcd + + - key: openshift.master.etcd.ping + description: etcd ping + type: int + applications: + - Openshift Etcd + ztriggers: - name: 'Application creation has failed on {HOST.NAME}' expression: '{Template Openshift Master:create_app.last(#1)}=1 and {Template Openshift Master:create_app.last(#2)}=1' @@ -56,3 +128,13 @@ g_template_openshift_master: expression: '{Template Openshift Master:openshift.project.counter.last()}=0' url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/openshift_master.asciidoc' priority: info + + - name: 'Low number of etcd watchers on {HOST.NAME}' + expression: '{Template Openshift Master:openshift.master.etcd.watchers.last(#1)}<10 and {Template Openshift Master:openshift.master.etcd.watchers.last(#2)}<10' + url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_etcd.asciidoc' + priority: avg + + - name: 'Etcd ping failed on {HOST.NAME}' + expression: '{Template Openshift Master:openshift.master.etcd.ping.last(#1)}=0 and {Template Openshift Master:openshift.master.etcd.ping.last(#2)}=0' + url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_etcd.asciidoc' + priority: high diff --git a/roles/os_zabbix/vars/template_os_linux.yml b/roles/os_zabbix/vars/template_os_linux.yml index aeeec4b8d..fbc20cd63 100644 --- a/roles/os_zabbix/vars/template_os_linux.yml +++ b/roles/os_zabbix/vars/template_os_linux.yml @@ -194,6 +194,11 @@ g_template_os_linux: lifetime: 1 description: "Dynamically register the filesystems" + - name: disc.disk + key: disc.disk + lifetime: 1 + description: "Dynamically register disks on a node" + zitemprototypes: - discoveryrule_key: disc.filesys name: "disc.filesys.full.{#OSO_FILESYS}" @@ -211,6 +216,22 @@ g_template_os_linux: applications: - Disk + - discoveryrule_key: disc.disk + name: "TPS (IOPS) for disk {#OSO_DISK}" + key: "disc.disk.tps[{#OSO_DISK}]" + value_type: int + description: "PCP disk.dev.totals metric measured over a period of time. This shows how many disk transactions per second the disk is using" + applications: + - Disk + + - discoveryrule_key: disc.disk + name: "Percent Utilized for disk {#OSO_DISK}" + key: "disc.disk.putil[{#OSO_DISK}]" + value_type: float + description: "PCP disk.dev.avactive metric measured over a period of time. This is the '%util' in the iostat command" + applications: + - Disk + ztriggerprototypes: - name: 'Filesystem: {#OSO_FILESYS} has less than 15% free disk space on {HOST.NAME}' expression: '{Template OS Linux:disc.filesys.full[{#OSO_FILESYS}].last()}>85' diff --git a/roles/os_zabbix/vars/template_performance_copilot.yml b/roles/os_zabbix/vars/template_performance_copilot.yml new file mode 100644 index 000000000..b62fa0228 --- /dev/null +++ b/roles/os_zabbix/vars/template_performance_copilot.yml @@ -0,0 +1,14 @@ +--- +g_template_performance_copilot: + name: Template Performance Copilot + zitems: + - key: pcp.ping + applications: + - Performance Copilot + value_type: int + + ztriggers: + - name: 'pcp.ping failed on {HOST.NAME}' + expression: '{Template Performance Copilot:pcp.ping.max(#3)}<1' + url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_pcp_ping.asciidoc' + priority: average diff --git a/test/units/README.md b/test/units/README.md index 3bed227eb..78a02c3ea 100644 --- a/test/units/README.md +++ b/test/units/README.md @@ -4,4 +4,4 @@ These should be run by sourcing the env-setup: $ source test/env-setup Then navigate to the test/units/ directory. -$ python -m unittest multi_ec2_test +$ python -m unittest multi_inventory_test diff --git a/test/units/multi_inventory_test.py b/test/units/multi_inventory_test.py new file mode 100755 index 000000000..168cd82b7 --- /dev/null +++ b/test/units/multi_inventory_test.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python2 +''' + Unit tests for MultiInventory +''' + +import unittest +import multi_inventory + +# Removing invalid variable names for tests so that I can +# keep them brief +# pylint: disable=invalid-name +class MultiInventoryTest(unittest.TestCase): + ''' + Test class for multiInventory + ''' + +# def setUp(self): +# '''setup method''' +# pass + + def test_merge_simple_1(self): + '''Testing a simple merge of 2 dictionaries''' + a = {"key1" : 1} + b = {"key1" : 2} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"key1": [1, 2]}) + + def test_merge_b_empty(self): + '''Testing a merge of an emtpy dictionary''' + a = {"key1" : 1} + b = {} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"key1": 1}) + + def test_merge_a_empty(self): + '''Testing a merge of an emtpy dictionary''' + b = {"key1" : 1} + a = {} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"key1": 1}) + + def test_merge_hash_array(self): + '''Testing a merge of a dictionary and a dictionary with an array''' + a = {"key1" : {"hasha": 1}} + b = {"key1" : [1, 2]} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"key1": [{"hasha": 1}, 1, 2]}) + + def test_merge_array_hash(self): + '''Testing a merge of a dictionary with an array and a dictionary with a hash''' + a = {"key1" : [1, 2]} + b = {"key1" : {"hasha": 1}} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"key1": [1, 2, {"hasha": 1}]}) + + def test_merge_keys_1(self): + '''Testing a merge on a dictionary for keys''' + a = {"key1" : [1, 2], "key2" : {"hasha": 2}} + b = {"key2" : {"hashb": 1}} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"key1": [1, 2], "key2": {"hasha": 2, "hashb": 1}}) + + def test_merge_recursive_1(self): + '''Testing a recursive merge''' + a = {"a" : {"b": {"c": 1}}} + b = {"a" : {"b": {"c": 2}}} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"a": {"b": {"c": [1, 2]}}}) + + def test_merge_recursive_array_item(self): + '''Testing a recursive merge for an array''' + a = {"a" : {"b": {"c": [1]}}} + b = {"a" : {"b": {"c": 2}}} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"a": {"b": {"c": [1, 2]}}}) + + def test_merge_recursive_hash_item(self): + '''Testing a recursive merge for a hash''' + a = {"a" : {"b": {"c": {"d": 1}}}} + b = {"a" : {"b": {"c": 2}}} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"a": {"b": {"c": [{"d": 1}, 2]}}}) + + def test_merge_recursive_array_hash(self): + '''Testing a recursive merge for an array and a hash''' + a = {"a" : [{"b": {"c": 1}}]} + b = {"a" : {"b": {"c": 1}}} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"a": [{"b": {"c": 1}}]}) + + def test_merge_recursive_hash_array(self): + '''Testing a recursive merge for an array and a hash''' + a = {"a" : {"b": {"c": 1}}} + b = {"a" : [{"b": {"c": 1}}]} + result = {} + _ = [multi_inventory.MultiInventory.merge_destructively(result, x) for x in [a, b]] + self.assertEqual(result, {"a": [{"b": {"c": 1}}]}) + +# def tearDown(self): +# '''TearDown method''' +# pass + +if __name__ == "__main__": + unittest.main() diff --git a/test/units/mutli_ec2_test.py b/test/units/mutli_ec2_test.py deleted file mode 100755 index 95df93cd2..000000000 --- a/test/units/mutli_ec2_test.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python2 - -import unittest -import sys -import os -import sys -import multi_ec2 - -class MultiEc2Test(unittest.TestCase): - - def setUp(self): - pass - - def test_merge_simple_1(self): - a = {"key1" : 1} - b = {"key1" : 2} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"key1": [1,2]}) - - def test_merge_b_empty(self): - a = {"key1" : 1} - b = {} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"key1": 1}) - - def test_merge_a_empty(self): - b = {"key1" : 1} - a = {} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"key1": 1}) - - def test_merge_hash_array(self): - a = {"key1" : {"hasha": 1}} - b = {"key1" : [1,2]} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"key1": [{"hasha": 1}, 1,2]}) - - def test_merge_array_hash(self): - a = {"key1" : [1,2]} - b = {"key1" : {"hasha": 1}} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"key1": [1,2, {"hasha": 1}]}) - - def test_merge_keys_1(self): - a = {"key1" : [1,2], "key2" : {"hasha": 2}} - b = {"key2" : {"hashb": 1}} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"key1": [1,2], "key2": {"hasha": 2, "hashb": 1}}) - - def test_merge_recursive_1(self): - a = {"a" : {"b": {"c": 1}}} - b = {"a" : {"b": {"c": 2}}} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"a": {"b": {"c": [1,2]}}}) - - def test_merge_recursive_array_item(self): - a = {"a" : {"b": {"c": [1]}}} - b = {"a" : {"b": {"c": 2}}} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"a": {"b": {"c": [1,2]}}}) - - def test_merge_recursive_hash_item(self): - a = {"a" : {"b": {"c": {"d": 1}}}} - b = {"a" : {"b": {"c": 2}}} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"a": {"b": {"c": [{"d": 1}, 2]}}}) - - def test_merge_recursive_array_hash(self): - a = {"a" : [{"b": {"c": 1}}]} - b = {"a" : {"b": {"c": 1}}} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"a": [{"b": {"c": 1}}]}) - - def test_merge_recursive_hash_array(self): - a = {"a" : {"b": {"c": 1}}} - b = {"a" : [{"b": {"c": 1}}]} - result = {} - [multi_ec2.MultiEc2.merge_destructively(result, x) for x in [a,b]] - self.assertEqual(result, {"a": [{"b": {"c": 1}}]}) - - def tearDown(self): - pass - -if __name__ == "__main__": - unittest.main() diff --git a/utils/setup.py b/utils/setup.py index 6e2fdd9c0..eac1b4b2e 100644 --- a/utils/setup.py +++ b/utils/setup.py @@ -79,7 +79,7 @@ setup( # pip to create the appropriate form of executable for the target platform. entry_points={ 'console_scripts': [ - 'oo-install=ooinstall.cli_installer:main', + 'oo-install=ooinstall.cli_installer:cli', ], }, ) diff --git a/utils/src/ooinstall/cli_installer.py b/utils/src/ooinstall/cli_installer.py index c2ae00bd1..d3ee8c51e 100644 --- a/utils/src/ooinstall/cli_installer.py +++ b/utils/src/ooinstall/cli_installer.py @@ -6,12 +6,13 @@ import click import os import re import sys -from ooinstall import install_transactions +from ooinstall import openshift_ansible from ooinstall import OOConfig from ooinstall.oo_config import Host from ooinstall.variants import find_variant, get_variant_version_combos DEFAULT_ANSIBLE_CONFIG = '/usr/share/atomic-openshift-util/ansible.cfg' +DEFAULT_PLAYBOOK_DIR = '/usr/share/ansible/openshift-ansible/' def validate_ansible_dir(path): if not path: @@ -94,7 +95,7 @@ The OpenShift Node provides the runtime environments for containers. It will host the required services to be managed by the Master. http://docs.openshift.com/enterprise/latest/architecture/infrastructure_components/kubernetes_infrastructure.html#master -http://docs.openshift.com/enterprise/3.0/architecture/infrastructure_components/kubernetes_infrastructure.html#node +http://docs.openshift.com/enterprise/latest/architecture/infrastructure_components/kubernetes_infrastructure.html#node """ click.echo(message) @@ -190,7 +191,7 @@ Notes: facts_confirmed = click.confirm("Do the above facts look correct?") if not facts_confirmed: message = """ -Edit %s with the desired values and rerun oo-install with --unattended . +Edit %s with the desired values and rerun atomic-openshift-installer with --unattended . """ % oo_cfg.config_path click.echo(message) # Make sure we actually write out the config file. @@ -356,8 +357,8 @@ def get_hosts_to_run_on(oo_cfg, callback_facts, unattended, force): hosts_to_run_on.extend(new_nodes) oo_cfg.hosts.extend(new_nodes) - install_transactions.set_config(oo_cfg) - callback_facts, error = install_transactions.default_facts(oo_cfg.hosts) + openshift_ansible.set_config(oo_cfg) + callback_facts, error = openshift_ansible.default_facts(oo_cfg.hosts) if error: click.echo("There was a problem fetching the required information. " \ "See {} for details.".format(oo_cfg.settings['ansible_log_path'])) @@ -367,71 +368,117 @@ def get_hosts_to_run_on(oo_cfg, callback_facts, unattended, force): return hosts_to_run_on, callback_facts -@click.command() + +@click.group() +@click.pass_context +@click.option('--unattended', '-u', is_flag=True, default=False) @click.option('--configuration', '-c', - type=click.Path(file_okay=True, - dir_okay=False, - writable=True, - readable=True), - default=None) + type=click.Path(file_okay=True, + dir_okay=False, + writable=True, + readable=True), + default=None) @click.option('--ansible-playbook-directory', '-a', type=click.Path(exists=True, file_okay=False, dir_okay=True, - writable=True, readable=True), # callback=validate_ansible_dir, + default='/usr/share/ansible/openshift-ansible/', envvar='OO_ANSIBLE_PLAYBOOK_DIRECTORY') @click.option('--ansible-config', - type=click.Path(file_okay=True, - dir_okay=False, - writable=True, - readable=True), - default=None) + type=click.Path(file_okay=True, + dir_okay=False, + writable=True, + readable=True), + default=None) @click.option('--ansible-log-path', - type=click.Path(file_okay=True, - dir_okay=False, - writable=True, - readable=True), - default="/tmp/ansible.log") -@click.option('--unattended', '-u', is_flag=True, default=False) -@click.option('--force', '-f', is_flag=True, default=False) + type=click.Path(file_okay=True, + dir_okay=False, + writable=True, + readable=True), + default="/tmp/ansible.log") #pylint: disable=too-many-arguments # Main CLI entrypoint, not much we can do about too many arguments. -def main(configuration, ansible_playbook_directory, ansible_config, ansible_log_path, unattended, force): - oo_cfg = OOConfig(configuration) +def cli(ctx, unattended, configuration, ansible_playbook_directory, ansible_config, ansible_log_path): + """ + The main click CLI module. Responsible for handling most common CLI options, + assigning any defaults and adding to the context for the sub-commands. + """ + ctx.obj = {} + ctx.obj['unattended'] = unattended + ctx.obj['configuration'] = configuration + ctx.obj['ansible_config'] = ansible_config + ctx.obj['ansible_log_path'] = ansible_log_path + oo_cfg = OOConfig(ctx.obj['configuration']) + + # If no playbook dir on the CLI, check the config: if not ansible_playbook_directory: ansible_playbook_directory = oo_cfg.settings.get('ansible_playbook_directory', '') + # If still no playbook dir, check for the default location: + if not ansible_playbook_directory and os.path.exists(DEFAULT_PLAYBOOK_DIR): + ansible_playbook_directory = DEFAULT_PLAYBOOK_DIR + validate_ansible_dir(ansible_playbook_directory) + oo_cfg.settings['ansible_playbook_directory'] = ansible_playbook_directory + oo_cfg.ansible_playbook_directory = ansible_playbook_directory + ctx.obj['ansible_playbook_directory'] = ansible_playbook_directory - if ansible_config: - oo_cfg.settings['ansible_config'] = ansible_config + if ctx.obj['ansible_config']: + oo_cfg.settings['ansible_config'] = ctx.obj['ansible_config'] elif os.path.exists(DEFAULT_ANSIBLE_CONFIG): # If we're installed by RPM this file should exist and we can use it as our default: oo_cfg.settings['ansible_config'] = DEFAULT_ANSIBLE_CONFIG - validate_ansible_dir(ansible_playbook_directory) - oo_cfg.settings['ansible_playbook_directory'] = ansible_playbook_directory - oo_cfg.ansible_playbook_directory = ansible_playbook_directory + oo_cfg.settings['ansible_log_path'] = ctx.obj['ansible_log_path'] + + ctx.obj['oo_cfg'] = oo_cfg + openshift_ansible.set_config(oo_cfg) + + +@click.command() +@click.pass_context +def uninstall(ctx): + oo_cfg = ctx.obj['oo_cfg'] + + if len(oo_cfg.hosts) == 0: + click.echo("No hosts defined in: %s" % oo_cfg['configuration']) + sys.exit(1) + + click.echo("OpenShift will be uninstalled from the following hosts:\n") + if not ctx.obj['unattended']: + # Prompt interactively to confirm: + for host in oo_cfg.hosts: + click.echo(" * %s" % host.name) + proceed = click.confirm("\nDo you wish to proceed?") + if not proceed: + click.echo("Uninstall cancelled.") + sys.exit(0) - oo_cfg.settings['ansible_log_path'] = ansible_log_path - install_transactions.set_config(oo_cfg) + openshift_ansible.run_uninstall_playbook() - if unattended: + + +@click.command() +@click.option('--force', '-f', is_flag=True, default=False) +@click.pass_context +def install(ctx, force): + oo_cfg = ctx.obj['oo_cfg'] + + if ctx.obj['unattended']: error_if_missing_info(oo_cfg) else: oo_cfg = get_missing_info_from_user(oo_cfg) click.echo('Gathering information from hosts...') - callback_facts, error = install_transactions.default_facts(oo_cfg.hosts) + callback_facts, error = openshift_ansible.default_facts(oo_cfg.hosts) if error: click.echo("There was a problem fetching the required information. " \ "Please see {} for details.".format(oo_cfg.settings['ansible_log_path'])) sys.exit(1) - hosts_to_run_on, callback_facts = get_hosts_to_run_on(oo_cfg, callback_facts, unattended, force) - + hosts_to_run_on, callback_facts = get_hosts_to_run_on(oo_cfg, callback_facts, ctx.obj['unattended'], force) click.echo('Writing config to: %s' % oo_cfg.config_path) @@ -449,10 +496,10 @@ def main(configuration, ansible_playbook_directory, ansible_config, ansible_log_ message = """ If changes are needed to the values recorded by the installer please update {}. """.format(oo_cfg.config_path) - if not unattended: + if not ctx.obj['unattended']: confirm_continue(message) - error = install_transactions.run_main_playbook(oo_cfg.hosts, + error = openshift_ansible.run_main_playbook(oo_cfg.hosts, hosts_to_run_on) if error: # The bootstrap script will print out the log location. @@ -475,5 +522,10 @@ http://docs.openshift.com/enterprise/latest/admin_guide/overview.html click.echo(message) click.pause() +cli.add_command(install) +cli.add_command(uninstall) + if __name__ == '__main__': - main() + # This is expected behaviour for context passing with click library: + # pylint: disable=unexpected-keyword-arg + cli(obj={}) diff --git a/utils/src/ooinstall/install_transactions.py b/utils/src/ooinstall/openshift_ansible.py index cef6662d7..0def72cfd 100644 --- a/utils/src/ooinstall/install_transactions.py +++ b/utils/src/ooinstall/openshift_ansible.py @@ -2,7 +2,9 @@ # repo. We will work on these over time. # pylint: disable=bad-continuation,missing-docstring,no-self-use,invalid-name,global-statement,global-variable-not-assigned +import socket import subprocess +import sys import os import yaml from ooinstall.variants import find_variant @@ -16,13 +18,15 @@ def set_config(cfg): def generate_inventory(hosts): print hosts global CFG + + installer_host = socket.gethostname() base_inventory_path = CFG.settings['ansible_inventory_path'] base_inventory = open(base_inventory_path, 'w') base_inventory.write('\n[OSEv3:children]\nmasters\nnodes\n') base_inventory.write('\n[OSEv3:vars]\n') base_inventory.write('ansible_ssh_user={}\n'.format(CFG.settings['ansible_ssh_user'])) if CFG.settings['ansible_ssh_user'] != 'root': - base_inventory.write('ansible_sudo=true\n') + base_inventory.write('ansible_become=true\n') # Find the correct deployment type for ansible: ver = find_variant(CFG.settings['variant'], @@ -41,6 +45,14 @@ def generate_inventory(hosts): if 'OO_INSTALL_STAGE_REGISTRY' in os.environ: base_inventory.write('oreg_url=registry.access.stage.redhat.com/openshift3/ose-${component}:${version}\n') + if any(host.hostname == installer_host or host.public_hostname == installer_host + for host in hosts): + no_pwd_sudo = subprocess.call(['sudo', '-v', '--non-interactive']) + if no_pwd_sudo == 1: + print 'The atomic-openshift-installer requires sudo access without a password.' + sys.exit(1) + base_inventory.write("ansible_connection=local\n") + base_inventory.write('\n[masters]\n') masters = (host for host in hosts if host.master) for master in masters: @@ -126,8 +138,20 @@ def run_main_playbook(hosts, hosts_to_run_on): facts_env['ANSIBLE_CONFIG'] = CFG.settings['ansible_config'] return run_ansible(main_playbook_path, inventory_file, facts_env) + def run_ansible(playbook, inventory, env_vars): return subprocess.call(['ansible-playbook', '--inventory-file={}'.format(inventory), playbook], env=env_vars) + +def run_uninstall_playbook(): + playbook = os.path.join(CFG.settings['ansible_playbook_directory'], + 'playbooks/adhoc/uninstall.yml') + inventory_file = generate_inventory(CFG.hosts) + facts_env = os.environ.copy() + if 'ansible_log_path' in CFG.settings: + facts_env['ANSIBLE_LOG_PATH'] = CFG.settings['ansible_log_path'] + if 'ansible_config' in CFG.settings: + facts_env['ANSIBLE_CONFIG'] = CFG.settings['ansible_config'] + return run_ansible(playbook, inventory_file, facts_env) diff --git a/utils/src/ooinstall/variants.py b/utils/src/ooinstall/variants.py index ed98429fc..05281d654 100644 --- a/utils/src/ooinstall/variants.py +++ b/utils/src/ooinstall/variants.py @@ -38,7 +38,7 @@ OSE = Variant('openshift-enterprise', 'OpenShift Enterprise', ] ) -AEP = Variant('atomic-enterprise', 'Atomic OpenShift Enterprise', +AEP = Variant('atomic-enterprise', 'Atomic Enterprise Platform', [ Version('3.1', 'atomic-enterprise') ] diff --git a/utils/test/cli_installer_tests.py b/utils/test/cli_installer_tests.py index 076fe5dc9..b183f0acb 100644 --- a/utils/test/cli_installer_tests.py +++ b/utils/test/cli_installer_tests.py @@ -76,7 +76,7 @@ class OOCliFixture(OOInstallFixture): self.cli_args = ["-a", self.work_dir] def run_cli(self): - return self.runner.invoke(cli.main, self.cli_args) + return self.runner.invoke(cli.cli, self.cli_args) def assert_result(self, result, exit_code): if result.exception is not None or result.exit_code != exit_code: @@ -102,8 +102,8 @@ class UnattendedCliTests(OOCliFixture): OOCliFixture.setUp(self) self.cli_args.append("-u") - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_cfg_full_run(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_playbook_mock.return_value = 0 @@ -111,8 +111,8 @@ class UnattendedCliTests(OOCliFixture): config_file = self.write_config(os.path.join(self.work_dir, 'ooinstall.conf'), SAMPLE_CONFIG % 'openshift-enterprise') - self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.extend(["-c", config_file, "install"]) + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) load_facts_args = load_facts_mock.call_args[0] @@ -133,8 +133,8 @@ class UnattendedCliTests(OOCliFixture): self.assertEquals(3, len(hosts)) self.assertEquals(3, len(hosts_to_run_on)) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_inventory_write(self, load_facts_mock, run_playbook_mock): # Add an ssh user so we can verify it makes it to the inventory file: @@ -146,8 +146,8 @@ class UnattendedCliTests(OOCliFixture): config_file = self.write_config(os.path.join(self.work_dir, 'ooinstall.conf'), merged_config) - self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.extend(["-c", config_file, "install"]) + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) # Check the inventory file looks as we would expect: @@ -172,8 +172,8 @@ class UnattendedCliTests(OOCliFixture): self.assertTrue('openshift_hostname' in master_line) self.assertTrue('openshift_public_hostname' in master_line) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_variant_version_latest_assumed(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) @@ -182,8 +182,8 @@ class UnattendedCliTests(OOCliFixture): config_file = self.write_config(os.path.join(self.work_dir, 'ooinstall.conf'), SAMPLE_CONFIG % 'openshift-enterprise') - self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.extend(["-c", config_file, "install"]) + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) written_config = self._read_yaml(config_file) @@ -199,8 +199,8 @@ class UnattendedCliTests(OOCliFixture): self.assertEquals('openshift-enterprise', inventory.get('OSEv3:vars', 'deployment_type')) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_variant_version_preserved(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) @@ -211,8 +211,8 @@ class UnattendedCliTests(OOCliFixture): config_file = self.write_config(os.path.join(self.work_dir, 'ooinstall.conf'), config) - self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.extend(["-c", config_file, "install"]) + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) written_config = self._read_yaml(config_file) @@ -227,8 +227,8 @@ class UnattendedCliTests(OOCliFixture): self.assertEquals('enterprise', inventory.get('OSEv3:vars', 'deployment_type')) - @patch('ooinstall.install_transactions.run_ansible') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_ansible') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_no_ansible_config_specified(self, load_facts_mock, run_ansible_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_ansible_mock.return_value = 0 @@ -238,8 +238,8 @@ class UnattendedCliTests(OOCliFixture): self._ansible_config_test(load_facts_mock, run_ansible_mock, config, None, None) - @patch('ooinstall.install_transactions.run_ansible') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_ansible') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_ansible_config_specified_cli(self, load_facts_mock, run_ansible_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_ansible_mock.return_value = 0 @@ -250,8 +250,8 @@ class UnattendedCliTests(OOCliFixture): self._ansible_config_test(load_facts_mock, run_ansible_mock, config, ansible_config, ansible_config) - @patch('ooinstall.install_transactions.run_ansible') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_ansible') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_ansible_config_specified_in_installer_config(self, load_facts_mock, run_ansible_mock): @@ -282,7 +282,8 @@ class UnattendedCliTests(OOCliFixture): self.cli_args.extend(["-c", config_file]) if ansible_config_cli: self.cli_args.extend(["--ansible-config", ansible_config_cli]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) # Test the env vars for facts playbook: @@ -388,8 +389,8 @@ class AttendedCliTests(OOCliFixture): self.assertTrue('public_ip' in h) self.assertTrue('public_hostname' in h) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_full_run(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_playbook_mock.return_value = 0 @@ -401,7 +402,8 @@ class AttendedCliTests(OOCliFixture): ssh_user='root', variant_num=1, confirm_facts='y') - result = self.runner.invoke(cli.main, self.cli_args, + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args, input=cli_input) self.assert_result(result, 0) @@ -411,8 +413,8 @@ class AttendedCliTests(OOCliFixture): written_config = self._read_yaml(self.config_file) self._verify_config_hosts(written_config, 3) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_add_nodes(self, load_facts_mock, run_playbook_mock): # Modify the mock facts to return a version indicating OpenShift @@ -432,7 +434,8 @@ class AttendedCliTests(OOCliFixture): ssh_user='root', variant_num=1, confirm_facts='y') - result = self.runner.invoke(cli.main, + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args, input=cli_input) self.assert_result(result, 0) @@ -443,8 +446,8 @@ class AttendedCliTests(OOCliFixture): written_config = self._read_yaml(self.config_file) self._verify_config_hosts(written_config, 3) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_fresh_install_with_config(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_playbook_mock.return_value = 0 @@ -454,7 +457,8 @@ class AttendedCliTests(OOCliFixture): SAMPLE_CONFIG % 'openshift-enterprise') cli_input = self._build_input(confirm_facts='y') self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args, input=cli_input) self.assert_result(result, 0) |