1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209 |
- #!/usr/bin/python
- # pylint: disable=too-many-lines
- # -*- coding: utf-8 -*-
- # vim: expandtab:tabstop=4:shiftwidth=4
- # Reason: Disable pylint too-many-lines because we don't want to split up this file.
- # Status: Permanently disabled to keep this module as self-contained as possible.
- """Ansible module for retrieving and setting openshift related facts"""
- import ConfigParser
- import copy
- import io
- import os
- import yaml
- from distutils.util import strtobool
- from distutils.version import LooseVersion
- import struct
- import socket
- from dbus import SystemBus, Interface
- from dbus.exceptions import DBusException
- DOCUMENTATION = '''
- ---
- module: openshift_facts
- short_description: Cluster Facts
- author: Jason DeTiberus
- requirements: [ ]
- '''
- EXAMPLES = '''
- '''
- def migrate_docker_facts(facts):
- """ Apply migrations for docker facts """
- params = {
- 'common': (
- 'additional_registries',
- 'insecure_registries',
- 'blocked_registries',
- 'options'
- ),
- 'node': (
- 'log_driver',
- 'log_options'
- )
- }
- if 'docker' not in facts:
- facts['docker'] = {}
- for role in params.keys():
- if role in facts:
- for param in params[role]:
- old_param = 'docker_' + param
- if old_param in facts[role]:
- facts['docker'][param] = facts[role].pop(old_param)
- if 'node' in facts and 'portal_net' in facts['node']:
- facts['docker']['hosted_registry_insecure'] = True
- facts['docker']['hosted_registry_network'] = facts['node'].pop('portal_net')
- # log_options was originally meant to be a comma separated string, but
- # we now prefer an actual list, with backward compatibility:
- if 'log_options' in facts['docker'] and \
- isinstance(facts['docker']['log_options'], basestring):
- facts['docker']['log_options'] = facts['docker']['log_options'].split(",")
- return facts
- # TODO: We should add a generic migration function that takes source and destination
- # paths and does the right thing rather than one function for common, one for node, etc.
- def migrate_common_facts(facts):
- """ Migrate facts from various roles into common """
- params = {
- 'node': ('portal_net'),
- 'master': ('portal_net')
- }
- if 'common' not in facts:
- facts['common'] = {}
- for role in params.keys():
- if role in facts:
- for param in params[role]:
- if param in facts[role]:
- facts['common'][param] = facts[role].pop(param)
- return facts
- def migrate_node_facts(facts):
- """ Migrate facts from various roles into node """
- params = {
- 'common': ('dns_ip'),
- }
- if 'node' not in facts:
- facts['node'] = {}
- for role in params.keys():
- if role in facts:
- for param in params[role]:
- if param in facts[role]:
- facts['node'][param] = facts[role].pop(param)
- return facts
- def migrate_local_facts(facts):
- """ Apply migrations of local facts """
- migrated_facts = copy.deepcopy(facts)
- migrated_facts = migrate_docker_facts(migrated_facts)
- migrated_facts = migrate_common_facts(migrated_facts)
- migrated_facts = migrate_node_facts(migrated_facts)
- migrated_facts = migrate_hosted_facts(migrated_facts)
- return migrated_facts
- def migrate_hosted_facts(facts):
- """ Apply migrations for master facts """
- if 'master' in facts:
- if 'router_selector' in facts['master']:
- if 'hosted' not in facts:
- facts['hosted'] = {}
- if 'router' not in facts['hosted']:
- facts['hosted']['router'] = {}
- facts['hosted']['router']['selector'] = facts['master'].pop('router_selector')
- if 'registry_selector' in facts['master']:
- if 'hosted' not in facts:
- facts['hosted'] = {}
- if 'registry' not in facts['hosted']:
- facts['hosted']['registry'] = {}
- facts['hosted']['registry']['selector'] = facts['master'].pop('registry_selector')
- return facts
- def first_ip(network):
- """ Return the first IPv4 address in network
- Args:
- network (str): network in CIDR format
- Returns:
- str: first IPv4 address
- """
- atoi = lambda addr: struct.unpack("!I", socket.inet_aton(addr))[0]
- itoa = lambda addr: socket.inet_ntoa(struct.pack("!I", addr))
- (address, netmask) = network.split('/')
- netmask_i = (0xffffffff << (32 - atoi(netmask))) & 0xffffffff
- return itoa((atoi(address) & netmask_i) + 1)
- def hostname_valid(hostname):
- """ Test if specified hostname should be considered valid
- Args:
- hostname (str): hostname to test
- Returns:
- bool: True if valid, otherwise False
- """
- if (not hostname or
- hostname.startswith('localhost') or
- hostname.endswith('localdomain') or
- hostname.endswith('novalocal') or
- len(hostname.split('.')) < 2):
- return False
- return True
- def choose_hostname(hostnames=None, fallback=''):
- """ Choose a hostname from the provided hostnames
- Given a list of hostnames and a fallback value, choose a hostname to
- use. This function will prefer fqdns if they exist (excluding any that
- begin with localhost or end with localdomain) over ip addresses.
- Args:
- hostnames (list): list of hostnames
- fallback (str): default value to set if hostnames does not contain
- a valid hostname
- Returns:
- str: chosen hostname
- """
- hostname = fallback
- if hostnames is None:
- return hostname
- ip_regex = r'\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z'
- ips = [i for i in hostnames
- if (i is not None and isinstance(i, basestring)
- and re.match(ip_regex, i))]
- hosts = [i for i in hostnames
- if i is not None and i != '' and i not in ips]
- for host_list in (hosts, ips):
- for host in host_list:
- if hostname_valid(host):
- return host
- return hostname
- def query_metadata(metadata_url, headers=None, expect_json=False):
- """ Return metadata from the provided metadata_url
- Args:
- metadata_url (str): metadata url
- headers (dict): headers to set for metadata request
- expect_json (bool): does the metadata_url return json
- Returns:
- dict or list: metadata request result
- """
- result, info = fetch_url(module, metadata_url, headers=headers)
- if info['status'] != 200:
- raise OpenShiftFactsMetadataUnavailableError("Metadata unavailable")
- if expect_json:
- return module.from_json(result.read())
- else:
- return [line.strip() for line in result.readlines()]
- def walk_metadata(metadata_url, headers=None, expect_json=False):
- """ Walk the metadata tree and return a dictionary of the entire tree
- Args:
- metadata_url (str): metadata url
- headers (dict): headers to set for metadata request
- expect_json (bool): does the metadata_url return json
- Returns:
- dict: the result of walking the metadata tree
- """
- metadata = dict()
- for line in query_metadata(metadata_url, headers, expect_json):
- if line.endswith('/') and not line == 'public-keys/':
- key = line[:-1]
- metadata[key] = walk_metadata(metadata_url + line,
- headers, expect_json)
- else:
- results = query_metadata(metadata_url + line, headers,
- expect_json)
- if len(results) == 1:
- # disable pylint maybe-no-member because overloaded use of
- # the module name causes pylint to not detect that results
- # is an array or hash
- # pylint: disable=maybe-no-member
- metadata[line] = results.pop()
- else:
- metadata[line] = results
- return metadata
- def get_provider_metadata(metadata_url, supports_recursive=False,
- headers=None, expect_json=False):
- """ Retrieve the provider metadata
- Args:
- metadata_url (str): metadata url
- supports_recursive (bool): does the provider metadata api support
- recursion
- headers (dict): headers to set for metadata request
- expect_json (bool): does the metadata_url return json
- Returns:
- dict: the provider metadata
- """
- try:
- if supports_recursive:
- metadata = query_metadata(metadata_url, headers,
- expect_json)
- else:
- metadata = walk_metadata(metadata_url, headers,
- expect_json)
- except OpenShiftFactsMetadataUnavailableError:
- metadata = None
- return metadata
- def normalize_gce_facts(metadata, facts):
- """ Normalize gce facts
- Args:
- metadata (dict): provider metadata
- facts (dict): facts to update
- Returns:
- dict: the result of adding the normalized metadata to the provided
- facts dict
- """
- for interface in metadata['instance']['networkInterfaces']:
- int_info = dict(ips=[interface['ip']], network_type='gce')
- int_info['public_ips'] = [ac['externalIp'] for ac
- in interface['accessConfigs']]
- int_info['public_ips'].extend(interface['forwardedIps'])
- _, _, network_id = interface['network'].rpartition('/')
- int_info['network_id'] = network_id
- facts['network']['interfaces'].append(int_info)
- _, _, zone = metadata['instance']['zone'].rpartition('/')
- facts['zone'] = zone
- # GCE currently only supports a single interface
- facts['network']['ip'] = facts['network']['interfaces'][0]['ips'][0]
- pub_ip = facts['network']['interfaces'][0]['public_ips'][0]
- facts['network']['public_ip'] = pub_ip
- facts['network']['hostname'] = metadata['instance']['hostname']
- # TODO: attempt to resolve public_hostname
- facts['network']['public_hostname'] = facts['network']['public_ip']
- return facts
- def normalize_aws_facts(metadata, facts):
- """ Normalize aws facts
- Args:
- metadata (dict): provider metadata
- facts (dict): facts to update
- Returns:
- dict: the result of adding the normalized metadata to the provided
- facts dict
- """
- for interface in sorted(
- metadata['network']['interfaces']['macs'].values(),
- key=lambda x: x['device-number']
- ):
- int_info = dict()
- var_map = {'ips': 'local-ipv4s', 'public_ips': 'public-ipv4s'}
- for ips_var, int_var in var_map.iteritems():
- ips = interface.get(int_var)
- if isinstance(ips, basestring):
- int_info[ips_var] = [ips]
- else:
- int_info[ips_var] = ips
- if 'vpc-id' in interface:
- int_info['network_type'] = 'vpc'
- else:
- int_info['network_type'] = 'classic'
- if int_info['network_type'] == 'vpc':
- int_info['network_id'] = interface['subnet-id']
- else:
- int_info['network_id'] = None
- facts['network']['interfaces'].append(int_info)
- facts['zone'] = metadata['placement']['availability-zone']
- # TODO: actually attempt to determine default local and public ips
- # by using the ansible default ip fact and the ipv4-associations
- # from the ec2 metadata
- facts['network']['ip'] = metadata.get('local-ipv4')
- facts['network']['public_ip'] = metadata.get('public-ipv4')
- # TODO: verify that local hostname makes sense and is resolvable
- facts['network']['hostname'] = metadata.get('local-hostname')
- # TODO: verify that public hostname makes sense and is resolvable
- facts['network']['public_hostname'] = metadata.get('public-hostname')
- return facts
- def normalize_openstack_facts(metadata, facts):
- """ Normalize openstack facts
- Args:
- metadata (dict): provider metadata
- facts (dict): facts to update
- Returns:
- dict: the result of adding the normalized metadata to the provided
- facts dict
- """
- # openstack ec2 compat api does not support network interfaces and
- # the version tested on did not include the info in the openstack
- # metadata api, should be updated if neutron exposes this.
- facts['zone'] = metadata['availability_zone']
- local_ipv4 = metadata['ec2_compat']['local-ipv4'].split(',')[0]
- facts['network']['ip'] = local_ipv4
- facts['network']['public_ip'] = metadata['ec2_compat']['public-ipv4']
- for f_var, h_var, ip_var in [('hostname', 'hostname', 'local-ipv4'),
- ('public_hostname', 'public-hostname', 'public-ipv4')]:
- try:
- if socket.gethostbyname(metadata['ec2_compat'][h_var]) == metadata['ec2_compat'][ip_var]:
- facts['network'][f_var] = metadata['ec2_compat'][h_var]
- else:
- facts['network'][f_var] = metadata['ec2_compat'][ip_var]
- except socket.gaierror:
- facts['network'][f_var] = metadata['ec2_compat'][ip_var]
- return facts
- def normalize_provider_facts(provider, metadata):
- """ Normalize provider facts
- Args:
- provider (str): host provider
- metadata (dict): provider metadata
- Returns:
- dict: the normalized provider facts
- """
- if provider is None or metadata is None:
- return {}
- # TODO: test for ipv6_enabled where possible (gce, aws do not support)
- # and configure ipv6 facts if available
- # TODO: add support for setting user_data if available
- facts = dict(name=provider, metadata=metadata,
- network=dict(interfaces=[], ipv6_enabled=False))
- if provider == 'gce':
- facts = normalize_gce_facts(metadata, facts)
- elif provider == 'aws':
- facts = normalize_aws_facts(metadata, facts)
- elif provider == 'openstack':
- facts = normalize_openstack_facts(metadata, facts)
- return facts
- def set_flannel_facts_if_unset(facts):
- """ Set flannel facts if not already present in facts dict
- dict: the facts dict updated with the flannel facts if
- missing
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the flannel
- facts if they were not already present
- """
- if 'common' in facts:
- if 'use_flannel' not in facts['common']:
- use_flannel = False
- facts['common']['use_flannel'] = use_flannel
- return facts
- def set_nuage_facts_if_unset(facts):
- """ Set nuage facts if not already present in facts dict
- dict: the facts dict updated with the nuage facts if
- missing
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the nuage
- facts if they were not already present
- """
- if 'common' in facts:
- if 'use_nuage' not in facts['common']:
- use_nuage = False
- facts['common']['use_nuage'] = use_nuage
- return facts
- def set_node_schedulability(facts):
- """ Set schedulable facts if not already present in facts dict
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the generated schedulable
- facts if they were not already present
- """
- if 'node' in facts:
- if 'schedulable' not in facts['node']:
- if 'master' in facts:
- facts['node']['schedulable'] = False
- else:
- facts['node']['schedulable'] = True
- return facts
- def set_selectors(facts):
- """ Set selectors facts if not already present in facts dict
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the generated selectors
- facts if they were not already present
- """
- deployment_type = facts['common']['deployment_type']
- if deployment_type == 'online':
- selector = "type=infra"
- else:
- selector = "region=infra"
- if 'hosted' not in facts:
- facts['hosted'] = {}
- if 'router' not in facts['hosted']:
- facts['hosted']['router'] = {}
- if 'selector' not in facts['hosted']['router'] or facts['hosted']['router']['selector'] in [None, 'None']:
- facts['hosted']['router']['selector'] = selector
- if 'registry' not in facts['hosted']:
- facts['hosted']['registry'] = {}
- if 'selector' not in facts['hosted']['registry'] or facts['hosted']['registry']['selector'] in [None, 'None']:
- facts['hosted']['registry']['selector'] = selector
- if 'metrics' not in facts['hosted']:
- facts['hosted']['metrics'] = {}
- if 'selector' not in facts['hosted']['metrics'] or facts['hosted']['metrics']['selector'] in [None, 'None']:
- facts['hosted']['metrics']['selector'] = None
- if 'logging' not in facts['hosted']:
- facts['hosted']['logging'] = {}
- if 'selector' not in facts['hosted']['logging'] or facts['hosted']['logging']['selector'] in [None, 'None']:
- facts['hosted']['logging']['selector'] = None
- return facts
- def set_dnsmasq_facts_if_unset(facts):
- """ Set dnsmasq facts if not already present in facts
- Args:
- facts (dict) existing facts
- Returns:
- facts (dict) updated facts with values set if not previously set
- """
- if 'common' in facts:
- if 'use_dnsmasq' not in facts['common']:
- facts['common']['use_dnsmasq'] = bool(safe_get_bool(facts['common']['version_gte_3_2_or_1_2']))
- if 'master' in facts and 'dns_port' not in facts['master']:
- if safe_get_bool(facts['common']['use_dnsmasq']):
- facts['master']['dns_port'] = 8053
- else:
- facts['master']['dns_port'] = 53
- return facts
- def set_project_cfg_facts_if_unset(facts):
- """ Set Project Configuration facts if not already present in facts dict
- dict:
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the generated Project Configuration
- facts if they were not already present
- """
- config = {
- 'default_node_selector': '',
- 'project_request_message': '',
- 'project_request_template': '',
- 'mcs_allocator_range': 's0:/2',
- 'mcs_labels_per_project': 5,
- 'uid_allocator_range': '1000000000-1999999999/10000'
- }
- if 'master' in facts:
- for key, value in config.items():
- if key not in facts['master']:
- facts['master'][key] = value
- return facts
- def set_identity_providers_if_unset(facts):
- """ Set identity_providers fact if not already present in facts dict
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the generated identity providers
- facts if they were not already present
- """
- if 'master' in facts:
- deployment_type = facts['common']['deployment_type']
- if 'identity_providers' not in facts['master']:
- identity_provider = dict(
- name='allow_all', challenge=True, login=True,
- kind='AllowAllPasswordIdentityProvider'
- )
- if deployment_type in ['enterprise', 'atomic-enterprise', 'openshift-enterprise']:
- identity_provider = dict(
- name='deny_all', challenge=True, login=True,
- kind='DenyAllPasswordIdentityProvider'
- )
- facts['master']['identity_providers'] = [identity_provider]
- return facts
- def set_url_facts_if_unset(facts):
- """ Set url facts if not already present in facts dict
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the generated url facts if they
- were not already present
- """
- if 'master' in facts:
- hostname = facts['common']['hostname']
- cluster_hostname = facts['master'].get('cluster_hostname')
- cluster_public_hostname = facts['master'].get('cluster_public_hostname')
- public_hostname = facts['common']['public_hostname']
- api_hostname = cluster_hostname if cluster_hostname else hostname
- api_public_hostname = cluster_public_hostname if cluster_public_hostname else public_hostname
- console_path = facts['master']['console_path']
- etcd_hosts = facts['master']['etcd_hosts']
- use_ssl = dict(
- api=facts['master']['api_use_ssl'],
- public_api=facts['master']['api_use_ssl'],
- loopback_api=facts['master']['api_use_ssl'],
- console=facts['master']['console_use_ssl'],
- public_console=facts['master']['console_use_ssl'],
- etcd=facts['master']['etcd_use_ssl']
- )
- ports = dict(
- api=facts['master']['api_port'],
- public_api=facts['master']['api_port'],
- loopback_api=facts['master']['api_port'],
- console=facts['master']['console_port'],
- public_console=facts['master']['console_port'],
- etcd=facts['master']['etcd_port'],
- )
- etcd_urls = []
- if etcd_hosts != '':
- facts['master']['etcd_port'] = ports['etcd']
- facts['master']['embedded_etcd'] = False
- for host in etcd_hosts:
- etcd_urls.append(format_url(use_ssl['etcd'], host,
- ports['etcd']))
- else:
- etcd_urls = [format_url(use_ssl['etcd'], hostname,
- ports['etcd'])]
- facts['master'].setdefault('etcd_urls', etcd_urls)
- prefix_hosts = [('api', api_hostname),
- ('public_api', api_public_hostname),
- ('loopback_api', hostname)]
- for prefix, host in prefix_hosts:
- facts['master'].setdefault(prefix + '_url', format_url(use_ssl[prefix],
- host,
- ports[prefix]))
- r_lhn = "{0}:{1}".format(hostname, ports['api']).replace('.', '-')
- r_lhu = "system:openshift-master/{0}:{1}".format(api_hostname, ports['api']).replace('.', '-')
- facts['master'].setdefault('loopback_cluster_name', r_lhn)
- facts['master'].setdefault('loopback_context_name', "default/{0}/system:openshift-master".format(r_lhn))
- facts['master'].setdefault('loopback_user', r_lhu)
- prefix_hosts = [('console', api_hostname), ('public_console', api_public_hostname)]
- for prefix, host in prefix_hosts:
- facts['master'].setdefault(prefix + '_url', format_url(use_ssl[prefix],
- host,
- ports[prefix],
- console_path))
- return facts
- def set_aggregate_facts(facts):
- """ Set aggregate facts
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with aggregated facts
- """
- all_hostnames = set()
- internal_hostnames = set()
- kube_svc_ip = first_ip(facts['common']['portal_net'])
- 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'])
- facts['common']['kube_svc_ip'] = kube_svc_ip
- internal_hostnames.add(facts['common']['hostname'])
- internal_hostnames.add(facts['common']['ip'])
- cluster_domain = facts['common']['dns_domain']
- if 'master' in facts:
- 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)
- all_hostnames.add(kube_svc_ip)
- internal_hostnames.add(kube_svc_ip)
- facts['common']['all_hostnames'] = list(all_hostnames)
- facts['common']['internal_hostnames'] = list(internal_hostnames)
- return facts
- def set_etcd_facts_if_unset(facts):
- """
- If using embedded etcd, loads the data directory from master-config.yaml.
- If using standalone etcd, loads ETCD_DATA_DIR from etcd.conf.
- If anything goes wrong parsing these, the fact will not be set.
- """
- if 'master' in facts and safe_get_bool(facts['master']['embedded_etcd']):
- etcd_facts = facts['etcd'] if 'etcd' in facts else dict()
- if 'etcd_data_dir' not in etcd_facts:
- try:
- # Parse master config to find actual etcd data dir:
- master_cfg_path = os.path.join(facts['common']['config_base'],
- 'master/master-config.yaml')
- master_cfg_f = open(master_cfg_path, 'r')
- config = yaml.safe_load(master_cfg_f.read())
- master_cfg_f.close()
- etcd_facts['etcd_data_dir'] = \
- config['etcdConfig']['storageDirectory']
- facts['etcd'] = etcd_facts
- # We don't want exceptions bubbling up here:
- # pylint: disable=broad-except
- except Exception:
- pass
- else:
- etcd_facts = facts['etcd'] if 'etcd' in facts else dict()
- # Read ETCD_DATA_DIR from /etc/etcd/etcd.conf:
- try:
- # Add a fake section for parsing:
- ini_str = unicode('[root]\n' + open('/etc/etcd/etcd.conf', 'r').read(), 'utf-8')
- ini_fp = io.StringIO(ini_str)
- config = ConfigParser.RawConfigParser()
- config.readfp(ini_fp)
- etcd_data_dir = config.get('root', 'ETCD_DATA_DIR')
- if etcd_data_dir.startswith('"') and etcd_data_dir.endswith('"'):
- etcd_data_dir = etcd_data_dir[1:-1]
- etcd_facts['etcd_data_dir'] = etcd_data_dir
- facts['etcd'] = etcd_facts
- # We don't want exceptions bubbling up here:
- # pylint: disable=broad-except
- except Exception:
- pass
- return facts
- def set_deployment_facts_if_unset(facts):
- """ Set Facts that vary based on deployment_type. This currently
- includes common.service_type, common.config_base, master.registry_url,
- node.registry_url, node.storage_plugin_deps
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the generated deployment_type
- facts
- """
- # 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']:
- service_type = 'atomic-openshift'
- if deployment_type == 'origin':
- service_type = 'origin'
- elif deployment_type in ['enterprise']:
- service_type = 'openshift'
- facts['common']['service_type'] = service_type
- if 'config_base' not in facts['common']:
- config_base = '/etc/origin'
- if deployment_type in ['enterprise']:
- config_base = '/etc/openshift'
- # Handle upgrade scenarios when symlinks don't yet exist:
- if not os.path.exists(config_base) and os.path.exists('/etc/openshift'):
- config_base = '/etc/openshift'
- facts['common']['config_base'] = config_base
- if 'data_dir' not in facts['common']:
- data_dir = '/var/lib/origin'
- if deployment_type in ['enterprise']:
- data_dir = '/var/lib/openshift'
- # Handle upgrade scenarios when symlinks don't yet exist:
- if not os.path.exists(data_dir) and os.path.exists('/var/lib/openshift'):
- data_dir = '/var/lib/openshift'
- facts['common']['data_dir'] = data_dir
- if 'docker' in facts:
- deployment_type = facts['common']['deployment_type']
- if deployment_type in ['enterprise', 'atomic-enterprise', 'openshift-enterprise']:
- addtl_regs = facts['docker'].get('additional_registries', [])
- ent_reg = 'registry.access.redhat.com'
- if ent_reg not in addtl_regs:
- facts['docker']['additional_registries'] = addtl_regs + [ent_reg]
- for role in ('master', 'node'):
- if role in facts:
- deployment_type = facts['common']['deployment_type']
- if 'registry_url' not in facts[role]:
- registry_url = 'openshift/origin-${component}:${version}'
- if deployment_type in ['enterprise', 'online', 'openshift-enterprise']:
- registry_url = 'openshift3/ose-${component}:${version}'
- elif deployment_type == 'atomic-enterprise':
- registry_url = 'aep3_beta/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 facts['common']['deployment_subtype'] == 'registry':
- facts['master']['disabled_features'] = openshift_features
- if 'node' in facts:
- deployment_type = facts['common']['deployment_type']
- if 'storage_plugin_deps' not in facts['node']:
- if deployment_type in ['openshift-enterprise', 'atomic-enterprise', 'origin']:
- facts['node']['storage_plugin_deps'] = ['ceph', 'glusterfs', 'iscsi']
- else:
- facts['node']['storage_plugin_deps'] = []
- return facts
- def set_version_facts_if_unset(facts):
- """ Set version facts. This currently includes common.version and
- common.version_gte_3_1_or_1_1.
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with version facts.
- """
- if 'common' in facts:
- deployment_type = facts['common']['deployment_type']
- version = get_openshift_version(facts)
- if version:
- facts['common']['version'] = version
- if deployment_type == 'origin':
- version_gte_3_1_or_1_1 = LooseVersion(version) >= LooseVersion('1.1.0')
- version_gte_3_1_1_or_1_1_1 = LooseVersion(version) >= LooseVersion('1.1.1')
- version_gte_3_2_or_1_2 = LooseVersion(version) >= LooseVersion('1.2.0')
- version_gte_3_3_or_1_3 = LooseVersion(version) >= LooseVersion('1.3.0')
- else:
- version_gte_3_1_or_1_1 = LooseVersion(version) >= LooseVersion('3.0.2.905')
- version_gte_3_1_1_or_1_1_1 = LooseVersion(version) >= LooseVersion('3.1.1')
- version_gte_3_2_or_1_2 = LooseVersion(version) >= LooseVersion('3.1.1.901')
- version_gte_3_3_or_1_3 = LooseVersion(version) >= LooseVersion('3.3.0')
- else:
- version_gte_3_1_or_1_1 = True
- version_gte_3_1_1_or_1_1_1 = True
- version_gte_3_2_or_1_2 = True
- version_gte_3_3_or_1_3 = False
- facts['common']['version_gte_3_1_or_1_1'] = version_gte_3_1_or_1_1
- facts['common']['version_gte_3_1_1_or_1_1_1'] = version_gte_3_1_1_or_1_1_1
- facts['common']['version_gte_3_2_or_1_2'] = version_gte_3_2_or_1_2
- facts['common']['version_gte_3_3_or_1_3'] = version_gte_3_3_or_1_3
- if version_gte_3_3_or_1_3:
- examples_content_version = 'v1.3'
- elif version_gte_3_2_or_1_2:
- examples_content_version = 'v1.2'
- elif version_gte_3_1_or_1_1:
- examples_content_version = 'v1.1'
- else:
- examples_content_version = 'v1.0'
- facts['common']['examples_content_version'] = examples_content_version
- return facts
- def set_manageiq_facts_if_unset(facts):
- """ Set manageiq facts. This currently includes common.use_manageiq.
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with version facts.
- Raises:
- OpenShiftFactsInternalError:
- """
- if 'common' not in facts:
- if 'version_gte_3_1_or_1_1' not in facts['common']:
- raise OpenShiftFactsInternalError(
- "Invalid invocation: The required facts are not set"
- )
- if 'use_manageiq' not in facts['common']:
- facts['common']['use_manageiq'] = facts['common']['version_gte_3_1_or_1_1']
- return facts
- def set_sdn_facts_if_unset(facts, system_facts):
- """ Set sdn facts if not already present in facts dict
- Args:
- facts (dict): existing facts
- system_facts (dict): ansible_facts
- Returns:
- dict: the facts dict updated with the generated sdn facts if they
- were not already present
- """
- if 'common' in facts:
- use_sdn = facts['common']['use_openshift_sdn']
- if not (use_sdn == '' or isinstance(use_sdn, bool)):
- use_sdn = safe_get_bool(use_sdn)
- facts['common']['use_openshift_sdn'] = use_sdn
- if 'sdn_network_plugin_name' not in facts['common']:
- plugin = 'redhat/openshift-ovs-subnet' if use_sdn else ''
- facts['common']['sdn_network_plugin_name'] = plugin
- if 'master' in facts:
- if 'sdn_cluster_network_cidr' not in facts['master']:
- facts['master']['sdn_cluster_network_cidr'] = '10.1.0.0/16'
- if 'sdn_host_subnet_length' not in facts['master']:
- facts['master']['sdn_host_subnet_length'] = '8'
- if 'node' in facts and 'sdn_mtu' not in facts['node']:
- node_ip = facts['common']['ip']
- # default MTU if interface MTU cannot be detected
- facts['node']['sdn_mtu'] = '1450'
- for val in system_facts.itervalues():
- if isinstance(val, dict) and 'mtu' in val:
- mtu = val['mtu']
- if 'ipv4' in val and val['ipv4'].get('address') == node_ip:
- facts['node']['sdn_mtu'] = str(mtu - 50)
- return facts
- def set_nodename(facts):
- if 'node' in facts and 'common' in facts:
- if 'cloudprovider' in facts and facts['cloudprovider']['kind'] == 'openstack':
- facts['node']['nodename'] = facts['provider']['metadata']['hostname'].replace('.novalocal', '')
- else:
- facts['node']['nodename'] = facts['common']['hostname'].lower()
- return facts
- def migrate_oauth_template_facts(facts):
- """
- Migrate an old oauth template fact to a newer format if it's present.
- The legacy 'oauth_template' fact was just a filename, and assumed you were
- setting the 'login' template.
- The new pluralized 'oauth_templates' fact is a dict mapping the template
- name to a filename.
- Simplify the code after this by merging the old fact into the new.
- """
- if 'master' in facts and 'oauth_template' in facts['master']:
- if 'oauth_templates' not in facts['master']:
- facts['master']['oauth_templates'] = {"login": facts['master']['oauth_template']}
- elif 'login' not in facts['master']['oauth_templates']:
- facts['master']['oauth_templates']['login'] = facts['master']['oauth_template']
- return facts
- def format_url(use_ssl, hostname, port, path=''):
- """ Format url based on ssl flag, hostname, port and path
- Args:
- use_ssl (bool): is ssl enabled
- hostname (str): hostname
- port (str): port
- path (str): url path
- Returns:
- str: The generated url string
- """
- scheme = 'https' if use_ssl else 'http'
- netloc = hostname
- if (use_ssl and port != '443') or (not use_ssl and port != '80'):
- netloc += ":%s" % port
- try:
- url = urlparse.urlunparse((scheme, netloc, path, '', '', ''))
- except AttributeError:
- # pylint: disable=undefined-variable
- url = urlunparse((scheme, netloc, path, '', '', ''))
- return url
- def get_current_config(facts):
- """ Get current openshift config
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the current openshift config
- """
- current_config = dict()
- roles = [role for role in facts if role not in ['common', 'provider']]
- for role in roles:
- if 'roles' in current_config:
- current_config['roles'].append(role)
- else:
- current_config['roles'] = [role]
- # TODO: parse the /etc/sysconfig/openshift-{master,node} config to
- # determine the location of files.
- # TODO: I suspect this isn't working right now, but it doesn't prevent
- # anything from working properly as far as I can tell, perhaps because
- # we override the kubeconfig path everywhere we use it?
- # Query kubeconfig settings
- kubeconfig_dir = '/var/lib/origin/openshift.local.certificates'
- if role == 'node':
- kubeconfig_dir = os.path.join(
- kubeconfig_dir, "node-%s" % facts['common']['hostname']
- )
- kubeconfig_path = os.path.join(kubeconfig_dir, '.kubeconfig')
- if (os.path.isfile('/usr/bin/openshift')
- and os.path.isfile(kubeconfig_path)):
- try:
- _, output, _ = module.run_command(
- ["/usr/bin/openshift", "ex", "config", "view", "-o",
- "json", "--kubeconfig=%s" % kubeconfig_path],
- check_rc=False
- )
- config = json.loads(output)
- cad = 'certificate-authority-data'
- try:
- for cluster in config['clusters']:
- config['clusters'][cluster][cad] = 'masked'
- except KeyError:
- pass
- try:
- for user in config['users']:
- config['users'][user][cad] = 'masked'
- config['users'][user]['client-key-data'] = 'masked'
- except KeyError:
- pass
- current_config['kubeconfig'] = config
- # override pylint broad-except warning, since we do not want
- # to bubble up any exceptions if oc config view
- # fails
- # pylint: disable=broad-except
- except Exception:
- pass
- return current_config
- def build_kubelet_args(facts):
- """ Build node kubelet_args """
- cloud_cfg_path = os.path.join(facts['common']['config_base'],
- 'cloudprovider')
- if 'node' in facts:
- kubelet_args = {}
- if 'cloudprovider' in facts:
- if 'kind' in facts['cloudprovider']:
- if facts['cloudprovider']['kind'] == 'aws':
- kubelet_args['cloud-provider'] = ['aws']
- kubelet_args['cloud-config'] = [cloud_cfg_path + '/aws.conf']
- if facts['cloudprovider']['kind'] == 'openstack':
- kubelet_args['cloud-provider'] = ['openstack']
- kubelet_args['cloud-config'] = [cloud_cfg_path + '/openstack.conf']
- if facts['cloudprovider']['kind'] == 'gce':
- kubelet_args['cloud-provider'] = ['gce']
- if kubelet_args != {}:
- facts = merge_facts({'node': {'kubelet_args': kubelet_args}}, facts, [], [])
- return facts
- def build_controller_args(facts):
- """ Build master controller_args """
- cloud_cfg_path = os.path.join(facts['common']['config_base'],
- 'cloudprovider')
- if 'master' in facts:
- controller_args = {}
- if 'cloudprovider' in facts:
- if 'kind' in facts['cloudprovider']:
- if facts['cloudprovider']['kind'] == 'aws':
- controller_args['cloud-provider'] = ['aws']
- controller_args['cloud-config'] = [cloud_cfg_path + '/aws.conf']
- if facts['cloudprovider']['kind'] == 'openstack':
- controller_args['cloud-provider'] = ['openstack']
- controller_args['cloud-config'] = [cloud_cfg_path + '/openstack.conf']
- if facts['cloudprovider']['kind'] == 'gce':
- controller_args['cloud-provider'] = ['gce']
- if controller_args != {}:
- facts = merge_facts({'master': {'controller_args': controller_args}}, facts, [], [])
- return facts
- def build_api_server_args(facts):
- """ Build master api_server_args """
- cloud_cfg_path = os.path.join(facts['common']['config_base'],
- 'cloudprovider')
- if 'master' in facts:
- api_server_args = {}
- if 'cloudprovider' in facts:
- if 'kind' in facts['cloudprovider']:
- if facts['cloudprovider']['kind'] == 'aws':
- api_server_args['cloud-provider'] = ['aws']
- api_server_args['cloud-config'] = [cloud_cfg_path + '/aws.conf']
- if facts['cloudprovider']['kind'] == 'openstack':
- api_server_args['cloud-provider'] = ['openstack']
- api_server_args['cloud-config'] = [cloud_cfg_path + '/openstack.conf']
- if facts['cloudprovider']['kind'] == 'gce':
- api_server_args['cloud-provider'] = ['gce']
- if api_server_args != {}:
- facts = merge_facts({'master': {'api_server_args': api_server_args}}, facts, [], [])
- return facts
- def is_service_running(service):
- """ Queries systemd through dbus to see if the service is running """
- service_running = False
- bus = SystemBus()
- systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1')
- manager = Interface(systemd, dbus_interface='org.freedesktop.systemd1.Manager')
- try:
- service_unit = service if service.endswith('.service') else manager.GetUnit('{0}.service'.format(service))
- service_proxy = bus.get_object('org.freedesktop.systemd1', str(service_unit))
- service_properties = Interface(service_proxy, dbus_interface='org.freedesktop.DBus.Properties')
- service_load_state = service_properties.Get('org.freedesktop.systemd1.Unit', 'LoadState')
- service_active_state = service_properties.Get('org.freedesktop.systemd1.Unit', 'ActiveState')
- if service_load_state == 'loaded' and service_active_state == 'active':
- service_running = True
- except DBusException:
- pass
- return service_running
- def get_version_output(binary, version_cmd):
- """ runs and returns the version output for a command """
- cmd = []
- for item in (binary, version_cmd):
- if isinstance(item, list):
- cmd.extend(item)
- else:
- cmd.append(item)
- if os.path.isfile(cmd[0]):
- _, output, _ = module.run_command(cmd)
- return output
- def get_docker_version_info():
- """ Parses and returns the docker version info """
- result = None
- if is_service_running('docker'):
- version_info = yaml.safe_load(get_version_output('/usr/bin/docker', 'version'))
- if 'Server' in version_info:
- result = {
- 'api_version': version_info['Server']['API version'],
- 'version': version_info['Server']['Version']
- }
- return result
- def get_openshift_version(facts):
- """ Get current version of openshift on the host.
- Checks a variety of ways ranging from fastest to slowest.
- Args:
- facts (dict): existing facts
- optional cli_image for pulling the version number
- Returns:
- version: the current openshift version
- """
- version = None
- # No need to run this method repeatedly on a system if we already know the
- # version
- if 'common' in facts:
- if 'version' in facts['common'] and facts['common']['version'] is not None:
- return facts['common']['version']
- if os.path.isfile('/usr/bin/openshift'):
- _, output, _ = module.run_command(['/usr/bin/openshift', 'version'])
- version = parse_openshift_version(output)
- elif 'common' in facts and 'is_containerized' in facts['common']:
- version = get_container_openshift_version(facts)
- # Handle containerized masters that have not yet been configured as a node.
- # This can be very slow and may get re-run multiple times, so we only use this
- # if other methods failed to find a version.
- if not version and os.path.isfile('/usr/local/bin/openshift'):
- _, output, _ = module.run_command(['/usr/local/bin/openshift', 'version'])
- version = parse_openshift_version(output)
- return version
- def get_container_openshift_version(facts):
- """
- If containerized, see if we can determine the installed version via the
- systemd environment files.
- """
- for filename in ['/etc/sysconfig/%s-master', '/etc/sysconfig/%s-node']:
- env_path = filename % facts['common']['service_type']
- if not os.path.exists(env_path):
- continue
- with open(env_path) as env_file:
- for line in env_file:
- if line.startswith("IMAGE_VERSION="):
- tag = line[len("IMAGE_VERSION="):].strip()
- # Remove leading "v" and any trailing release info, we just want
- # a version number here:
- version = tag[1:].split("-")[0]
- return version
- return None
- def parse_openshift_version(output):
- """ Apply provider facts to supplied facts dict
- Args:
- string: output of 'openshift version'
- Returns:
- string: the version number
- """
- versions = dict(e.split(' v') for e in output.splitlines() if ' v' in e)
- ver = versions.get('openshift', '')
- # Remove trailing build number and commit hash from older versions, we need to return a straight
- # w.x.y.z version here for use as openshift_version throughout the playbooks/roles. (i.e. 3.1.1.6-64-g80b61da)
- ver = ver.split('-')[0]
- return ver
- def apply_provider_facts(facts, provider_facts):
- """ Apply provider facts to supplied facts dict
- Args:
- facts (dict): facts dict to update
- provider_facts (dict): provider facts to apply
- roles: host roles
- Returns:
- dict: the merged facts
- """
- if not provider_facts:
- return facts
- common_vars = [('hostname', 'ip'), ('public_hostname', 'public_ip')]
- for h_var, ip_var in common_vars:
- ip_value = provider_facts['network'].get(ip_var)
- if ip_value:
- facts['common'][ip_var] = ip_value
- facts['common'][h_var] = choose_hostname(
- [provider_facts['network'].get(h_var)],
- facts['common'][h_var]
- )
- facts['provider'] = provider_facts
- return facts
- # Disabling pylint too many branches. This function needs refactored
- # but is a very core part of openshift_facts.
- # pylint: disable=too-many-branches
- def merge_facts(orig, new, additive_facts_to_overwrite, protected_facts_to_overwrite):
- """ Recursively merge facts dicts
- Args:
- orig (dict): existing facts
- new (dict): facts to update
- additive_facts_to_overwrite (list): additive facts to overwrite in jinja
- '.' notation ex: ['master.named_certificates']
- protected_facts_to_overwrite (list): protected facts to overwrite in jinja
- '.' notation ex: ['master.master_count']
- Returns:
- dict: the merged facts
- """
- additive_facts = ['named_certificates']
- protected_facts = ['ha', 'master_count']
- # Facts we do not ever want to merge. These originate in inventory variables
- # and contain JSON dicts. We don't ever want to trigger a merge
- # here, just completely overwrite with the new if they are present there.
- inventory_json_facts = ['admission_plugin_config',
- 'kube_admission_plugin_config',
- 'image_policy_config']
- facts = dict()
- for key, value in orig.iteritems():
- # Key exists in both old and new facts.
- if key in new:
- if key in inventory_json_facts:
- # Watchout for JSON facts that sometimes load as strings.
- # (can happen if the JSON contains a boolean)
- if isinstance(new[key], basestring):
- facts[key] = yaml.safe_load(new[key])
- else:
- facts[key] = copy.deepcopy(new[key])
- # Continue to recurse if old and new fact is a dictionary.
- elif isinstance(value, dict) and isinstance(new[key], dict):
- # Collect the subset of additive facts to overwrite if
- # key matches. These will be passed to the subsequent
- # merge_facts call.
- relevant_additive_facts = []
- for item in additive_facts_to_overwrite:
- if '.' in item and item.startswith(key + '.'):
- relevant_additive_facts.append(item)
- # Collect the subset of protected facts to overwrite
- # if key matches. These will be passed to the
- # subsequent merge_facts call.
- relevant_protected_facts = []
- for item in protected_facts_to_overwrite:
- if '.' in item and item.startswith(key + '.'):
- relevant_protected_facts.append(item)
- facts[key] = merge_facts(value, new[key], relevant_additive_facts, relevant_protected_facts)
- # Key matches an additive fact and we are not overwriting
- # it so we will append the new value to the existing value.
- elif key in additive_facts and key not in [x.split('.')[-1] for x in additive_facts_to_overwrite]:
- if isinstance(value, list) and isinstance(new[key], list):
- new_fact = []
- for item in copy.deepcopy(value) + copy.deepcopy(new[key]):
- if item not in new_fact:
- new_fact.append(item)
- facts[key] = new_fact
- # Key matches a protected fact and we are not overwriting
- # it so we will determine if it is okay to change this
- # fact.
- elif key in protected_facts and key not in [x.split('.')[-1] for x in protected_facts_to_overwrite]:
- # The master count (int) can only increase unless it
- # has been passed as a protected fact to overwrite.
- if key == 'master_count':
- if int(value) <= int(new[key]):
- facts[key] = copy.deepcopy(new[key])
- else:
- module.fail_json(msg='openshift_facts received a lower value for openshift.master.master_count')
- # ha (bool) can not change unless it has been passed
- # as a protected fact to overwrite.
- if key == 'ha':
- if safe_get_bool(value) != safe_get_bool(new[key]):
- module.fail_json(msg='openshift_facts received a different value for openshift.master.ha')
- else:
- facts[key] = value
- # No other condition has been met. Overwrite the old fact
- # with the new value.
- else:
- facts[key] = copy.deepcopy(new[key])
- # Key isn't in new so add it to facts to keep it.
- else:
- facts[key] = copy.deepcopy(value)
- new_keys = set(new.keys()) - set(orig.keys())
- for key in new_keys:
- # Watchout for JSON facts that sometimes load as strings.
- # (can happen if the JSON contains a boolean)
- if key in inventory_json_facts and isinstance(new[key], basestring):
- facts[key] = yaml.safe_load(new[key])
- else:
- facts[key] = copy.deepcopy(new[key])
- return facts
- def save_local_facts(filename, facts):
- """ Save local facts
- Args:
- filename (str): local facts file
- facts (dict): facts to set
- """
- try:
- fact_dir = os.path.dirname(filename)
- if not os.path.exists(fact_dir):
- os.makedirs(fact_dir)
- with open(filename, 'w') as fact_file:
- fact_file.write(module.jsonify(facts))
- os.chmod(filename, 0o600)
- except (IOError, OSError) as ex:
- raise OpenShiftFactsFileWriteError(
- "Could not create fact file: %s, error: %s" % (filename, ex)
- )
- def get_local_facts_from_file(filename):
- """ Retrieve local facts from fact file
- Args:
- filename (str): local facts file
- Returns:
- dict: the retrieved facts
- """
- local_facts = dict()
- try:
- # Handle conversion of INI style facts file to json style
- ini_facts = ConfigParser.SafeConfigParser()
- ini_facts.read(filename)
- for section in ini_facts.sections():
- local_facts[section] = dict()
- for key, value in ini_facts.items(section):
- local_facts[section][key] = value
- except (ConfigParser.MissingSectionHeaderError,
- ConfigParser.ParsingError):
- try:
- with open(filename, 'r') as facts_file:
- local_facts = json.load(facts_file)
- except (ValueError, IOError):
- pass
- return local_facts
- def sort_unique(alist):
- """ Sorts and de-dupes a list
- Args:
- list: a list
- Returns:
- list: a sorted de-duped list
- """
- alist.sort()
- out = list()
- for i in alist:
- if i not in out:
- out.append(i)
- return out
- def safe_get_bool(fact):
- """ Get a boolean fact safely.
- Args:
- facts: fact to convert
- Returns:
- bool: given fact as a bool
- """
- return bool(strtobool(str(fact)))
- def set_proxy_facts(facts):
- """ Set global proxy facts and promote defaults from http_proxy, https_proxy,
- no_proxy to the more specific builddefaults and builddefaults_git vars.
- 1. http_proxy, https_proxy, no_proxy
- 2. builddefaults_*
- 3. builddefaults_git_*
- Args:
- facts(dict): existing facts
- Returns:
- facts(dict): Updated facts with missing values
- """
- if 'common' in facts:
- common = facts['common']
- if 'http_proxy' in common or 'https_proxy' in common:
- if 'no_proxy' in common and \
- isinstance(common['no_proxy'], basestring):
- common['no_proxy'] = common['no_proxy'].split(",")
- elif 'no_proxy' not in common:
- common['no_proxy'] = []
- if 'generate_no_proxy_hosts' in common and \
- safe_get_bool(common['generate_no_proxy_hosts']):
- if 'no_proxy_internal_hostnames' in common:
- common['no_proxy'].extend(common['no_proxy_internal_hostnames'].split(','))
- common['no_proxy'].append('.' + common['dns_domain'])
- # We always add ourselves no matter what
- common['no_proxy'].append(common['hostname'])
- common['no_proxy'] = sort_unique(common['no_proxy'])
- facts['common'] = common
- if 'builddefaults' in facts:
- builddefaults = facts['builddefaults']
- common = facts['common']
- # Copy values from common to builddefaults
- if 'http_proxy' not in builddefaults and 'http_proxy' in common:
- builddefaults['http_proxy'] = common['http_proxy']
- if 'https_proxy' not in builddefaults and 'https_proxy' in common:
- builddefaults['https_proxy'] = common['https_proxy']
- # make no_proxy into a list if it's not
- if 'no_proxy' in builddefaults and isinstance(builddefaults['no_proxy'], basestring):
- builddefaults['no_proxy'] = builddefaults['no_proxy'].split(",")
- if 'no_proxy' not in builddefaults and 'no_proxy' in common:
- builddefaults['no_proxy'] = common['no_proxy']
- if 'git_http_proxy' not in builddefaults and 'http_proxy' in builddefaults:
- builddefaults['git_http_proxy'] = builddefaults['http_proxy']
- if 'git_https_proxy' not in builddefaults and 'https_proxy' in builddefaults:
- builddefaults['git_https_proxy'] = builddefaults['https_proxy']
- # If we're actually defining a proxy config then create kube_admission_plugin_config
- # if it doesn't exist, then merge builddefaults[config] structure
- # into kube_admission_plugin_config
- if 'kube_admission_plugin_config' not in facts['master']:
- facts['master']['kube_admission_plugin_config'] = dict()
- if 'config' in builddefaults and ('http_proxy' in builddefaults or \
- 'https_proxy' in builddefaults):
- facts['master']['kube_admission_plugin_config'].update(builddefaults['config'])
- facts['builddefaults'] = builddefaults
- return facts
- # pylint: disable=too-many-statements
- def set_container_facts_if_unset(facts):
- """ Set containerized facts.
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with the generated containerization
- facts
- """
- deployment_type = facts['common']['deployment_type']
- if deployment_type in ['enterprise', 'openshift-enterprise']:
- master_image = 'openshift3/ose'
- cli_image = master_image
- node_image = 'openshift3/node'
- ovs_image = 'openshift3/openvswitch'
- etcd_image = 'registry.access.redhat.com/rhel7/etcd'
- pod_image = 'openshift3/ose-pod'
- router_image = 'openshift3/ose-haproxy-router'
- registry_image = 'openshift3/ose-docker-registry'
- deployer_image = 'openshift3/ose-deployer'
- elif deployment_type == 'atomic-enterprise':
- master_image = 'aep3_beta/aep'
- cli_image = master_image
- node_image = 'aep3_beta/node'
- ovs_image = 'aep3_beta/openvswitch'
- etcd_image = 'registry.access.redhat.com/rhel7/etcd'
- pod_image = 'aep3_beta/aep-pod'
- router_image = 'aep3_beta/aep-haproxy-router'
- registry_image = 'aep3_beta/aep-docker-registry'
- deployer_image = 'aep3_beta/aep-deployer'
- else:
- master_image = 'openshift/origin'
- cli_image = master_image
- node_image = 'openshift/node'
- ovs_image = 'openshift/openvswitch'
- etcd_image = 'registry.access.redhat.com/rhel7/etcd'
- pod_image = 'openshift/origin-pod'
- router_image = 'openshift/origin-haproxy-router'
- registry_image = 'openshift/origin-docker-registry'
- deployer_image = 'openshift/origin-deployer'
- facts['common']['is_atomic'] = os.path.isfile('/run/ostree-booted')
- if 'is_containerized' not in facts['common']:
- facts['common']['is_containerized'] = facts['common']['is_atomic']
- if 'cli_image' not in facts['common']:
- facts['common']['cli_image'] = cli_image
- if 'pod_image' not in facts['common']:
- facts['common']['pod_image'] = pod_image
- if 'router_image' not in facts['common']:
- facts['common']['router_image'] = router_image
- if 'registry_image' not in facts['common']:
- facts['common']['registry_image'] = registry_image
- if 'deployer_image' not in facts['common']:
- facts['common']['deployer_image'] = deployer_image
- if 'etcd' in facts and 'etcd_image' not in facts['etcd']:
- facts['etcd']['etcd_image'] = etcd_image
- if 'master' in facts and 'master_image' not in facts['master']:
- facts['master']['master_image'] = master_image
- if 'node' in facts:
- if 'node_image' not in facts['node']:
- facts['node']['node_image'] = node_image
- if 'ovs_image' not in facts['node']:
- facts['node']['ovs_image'] = ovs_image
- if safe_get_bool(facts['common']['is_containerized']):
- facts['common']['admin_binary'] = '/usr/local/bin/oadm'
- facts['common']['client_binary'] = '/usr/local/bin/oc'
- return facts
- def set_installed_variant_rpm_facts(facts):
- """ Set RPM facts of installed variant
- Args:
- facts (dict): existing facts
- Returns:
- dict: the facts dict updated with installed_variant_rpms
- """
- installed_rpms = []
- for base_rpm in ['openshift', 'atomic-openshift', 'origin']:
- optional_rpms = ['master', 'node', 'clients', 'sdn-ovs']
- variant_rpms = [base_rpm] + \
- ['{0}-{1}'.format(base_rpm, r) for r in optional_rpms] + \
- ['tuned-profiles-%s-node' % base_rpm]
- for rpm in variant_rpms:
- exit_code, _, _ = module.run_command(['rpm', '-q', rpm])
- if exit_code == 0:
- installed_rpms.append(rpm)
- facts['common']['installed_variant_rpms'] = installed_rpms
- return facts
- class OpenShiftFactsInternalError(Exception):
- """Origin Facts Error"""
- pass
- class OpenShiftFactsUnsupportedRoleError(Exception):
- """Origin Facts Unsupported Role Error"""
- pass
- class OpenShiftFactsFileWriteError(Exception):
- """Origin Facts File Write Error"""
- pass
- class OpenShiftFactsMetadataUnavailableError(Exception):
- """Origin Facts Metadata Unavailable Error"""
- pass
- class OpenShiftFacts(object):
- """ Origin Facts
- Attributes:
- facts (dict): facts for the host
- Args:
- module (AnsibleModule): an AnsibleModule object
- role (str): role for setting local facts
- filename (str): local facts file to use
- local_facts (dict): local facts to set
- additive_facts_to_overwrite (list): additive facts to overwrite in jinja
- '.' notation ex: ['master.named_certificates']
- protected_facts_to_overwrite (list): protected facts to overwrite in jinja
- '.' notation ex: ['master.master_count']
- Raises:
- OpenShiftFactsUnsupportedRoleError:
- """
- known_roles = ['builddefaults',
- 'clock',
- 'cloudprovider',
- 'common',
- 'docker',
- 'etcd',
- 'hosted',
- 'master',
- 'node']
- # Disabling too-many-arguments, this should be cleaned up as a TODO item.
- # pylint: disable=too-many-arguments,no-value-for-parameter
- def __init__(self, role, filename, local_facts,
- additive_facts_to_overwrite=None,
- openshift_env=None,
- openshift_env_structures=None,
- protected_facts_to_overwrite=None):
- self.changed = False
- self.filename = filename
- if role not in self.known_roles:
- raise OpenShiftFactsUnsupportedRoleError(
- "Role %s is not supported by this module" % role
- )
- self.role = role
- try:
- # ansible-2.1
- # pylint: disable=too-many-function-args,invalid-name
- self.system_facts = ansible_facts(module, ['hardware', 'network', 'virtual', 'facter'])
- for (k, v) in self.system_facts.items():
- self.system_facts["ansible_%s" % k.replace('-', '_')] = v
- except UnboundLocalError:
- # ansible-2.2
- self.system_facts = get_all_facts(module)['ansible_facts']
- self.facts = self.generate_facts(local_facts,
- additive_facts_to_overwrite,
- openshift_env,
- openshift_env_structures,
- protected_facts_to_overwrite)
- def generate_facts(self,
- local_facts,
- additive_facts_to_overwrite,
- openshift_env,
- openshift_env_structures,
- protected_facts_to_overwrite):
- """ Generate facts
- Args:
- local_facts (dict): local_facts for overriding generated defaults
- additive_facts_to_overwrite (list): additive facts to overwrite in jinja
- '.' notation ex: ['master.named_certificates']
- openshift_env (dict): openshift_env facts for overriding generated defaults
- protected_facts_to_overwrite (list): protected facts to overwrite in jinja
- '.' notation ex: ['master.master_count']
- Returns:
- dict: The generated facts
- """
- local_facts = self.init_local_facts(local_facts,
- additive_facts_to_overwrite,
- openshift_env,
- openshift_env_structures,
- protected_facts_to_overwrite)
- roles = local_facts.keys()
- if 'common' in local_facts and 'deployment_type' in local_facts['common']:
- deployment_type = local_facts['common']['deployment_type']
- else:
- deployment_type = 'origin'
- if 'common' in local_facts and 'deployment_subtype' in local_facts['common']:
- deployment_subtype = local_facts['common']['deployment_subtype']
- else:
- deployment_subtype = 'basic'
- defaults = self.get_defaults(roles, deployment_type, deployment_subtype)
- provider_facts = self.init_provider_facts()
- facts = apply_provider_facts(defaults, provider_facts)
- facts = merge_facts(facts,
- local_facts,
- additive_facts_to_overwrite,
- protected_facts_to_overwrite)
- facts = migrate_oauth_template_facts(facts)
- facts['current_config'] = get_current_config(facts)
- facts = set_url_facts_if_unset(facts)
- facts = set_project_cfg_facts_if_unset(facts)
- facts = set_flannel_facts_if_unset(facts)
- facts = set_nuage_facts_if_unset(facts)
- facts = set_node_schedulability(facts)
- facts = set_selectors(facts)
- facts = set_identity_providers_if_unset(facts)
- facts = set_sdn_facts_if_unset(facts, self.system_facts)
- facts = set_deployment_facts_if_unset(facts)
- facts = set_container_facts_if_unset(facts)
- facts = build_kubelet_args(facts)
- facts = build_controller_args(facts)
- facts = build_api_server_args(facts)
- facts = set_version_facts_if_unset(facts)
- facts = set_dnsmasq_facts_if_unset(facts)
- facts = set_manageiq_facts_if_unset(facts)
- facts = set_aggregate_facts(facts)
- facts = set_etcd_facts_if_unset(facts)
- facts = set_proxy_facts(facts)
- if not safe_get_bool(facts['common']['is_containerized']):
- facts = set_installed_variant_rpm_facts(facts)
- facts = set_nodename(facts)
- return dict(openshift=facts)
- def get_defaults(self, roles, deployment_type, deployment_subtype):
- """ Get default fact values
- Args:
- roles (list): list of roles for this host
- Returns:
- dict: The generated default facts
- """
- defaults = {}
- ip_addr = self.system_facts['ansible_default_ipv4']['address']
- exit_code, output, _ = module.run_command(['hostname', '-f'])
- hostname_f = output.strip() if exit_code == 0 else ''
- hostname_values = [hostname_f, self.system_facts['ansible_nodename'],
- self.system_facts['ansible_fqdn']]
- hostname = choose_hostname(hostname_values, ip_addr)
- defaults['common'] = dict(use_openshift_sdn=True, ip=ip_addr,
- public_ip=ip_addr,
- deployment_type=deployment_type,
- deployment_subtype=deployment_subtype,
- hostname=hostname,
- public_hostname=hostname,
- portal_net='172.30.0.0/16',
- client_binary='oc', admin_binary='oadm',
- dns_domain='cluster.local',
- install_examples=True,
- debug_level=2)
- if 'master' in roles:
- scheduler_predicates = [
- {"name": "MatchNodeSelector"},
- {"name": "PodFitsResources"},
- {"name": "PodFitsPorts"},
- {"name": "NoDiskConflict"},
- {"name": "NoVolumeZoneConflict"},
- {"name": "MaxEBSVolumeCount"},
- {"name": "MaxGCEPDVolumeCount"},
- {"name": "Region", "argument": {"serviceAffinity" : {"labels" : ["region"]}}}
- ]
- scheduler_priorities = [
- {"name": "LeastRequestedPriority", "weight": 1},
- {"name": "SelectorSpreadPriority", "weight": 1},
- {"name": "Zone", "weight" : 2, "argument": {"serviceAntiAffinity" : {"label": "zone"}}}
- ]
- defaults['master'] = dict(api_use_ssl=True, api_port='8443',
- controllers_port='8444',
- console_use_ssl=True,
- console_path='/console',
- console_port='8443', etcd_use_ssl=True,
- etcd_hosts='', etcd_port='4001',
- portal_net='172.30.0.0/16',
- embedded_etcd=True, embedded_kube=True,
- embedded_dns=True,
- bind_addr='0.0.0.0',
- session_max_seconds=3600,
- session_name='ssn',
- session_secrets_file='',
- access_token_max_seconds=86400,
- auth_token_max_seconds=500,
- oauth_grant_method='auto',
- scheduler_predicates=scheduler_predicates,
- scheduler_priorities=scheduler_priorities,
- dynamic_provisioning_enabled=True,
- max_requests_inflight=500)
- if 'node' in roles:
- defaults['node'] = dict(labels={}, annotations={},
- iptables_sync_period='30s',
- local_quota_per_fsgroup="",
- set_node_ip=False)
- if 'docker' in roles:
- docker = dict(disable_push_dockerhub=False,
- hosted_registry_insecure=True,
- options='--log-driver=json-file --log-opt max-size=50m')
- version_info = get_docker_version_info()
- if version_info is not None:
- docker['api_version'] = version_info['api_version']
- docker['version'] = version_info['version']
- docker['gte_1_10'] = LooseVersion(version_info['version']) >= LooseVersion('1.10')
- defaults['docker'] = docker
- if 'clock' in roles:
- exit_code, _, _ = module.run_command(['rpm', '-q', 'chrony'])
- chrony_installed = bool(exit_code == 0)
- defaults['clock'] = dict(
- enabled=True,
- chrony_installed=chrony_installed)
- if 'cloudprovider' in roles:
- defaults['cloudprovider'] = dict(kind=None)
- if 'hosted' in roles or self.role == 'hosted':
- defaults['hosted'] = dict(
- metrics=dict(
- deploy=False,
- duration=7,
- resolution='10s',
- storage=dict(
- kind=None,
- volume=dict(
- name='metrics',
- size='10Gi'
- ),
- nfs=dict(
- directory='/exports',
- options='*(rw,root_squash)'
- ),
- host=None,
- access_modes=['ReadWriteOnce'],
- create_pv=True,
- create_pvc=False
- )
- ),
- logging=dict(
- storage=dict(
- kind=None,
- volume=dict(
- name='logging-es',
- size='10Gi'
- ),
- nfs=dict(
- directory='/exports',
- options='*(rw,root_squash)'
- ),
- host=None,
- access_modes=['ReadWriteOnce'],
- create_pv=True,
- create_pvc=False
- )
- ),
- registry=dict(
- storage=dict(
- kind=None,
- volume=dict(
- name='registry',
- size='5Gi'
- ),
- nfs=dict(
- directory='/exports',
- options='*(rw,root_squash)'),
- host=None,
- access_modes=['ReadWriteMany'],
- create_pv=True,
- create_pvc=True
- )
- ),
- router=dict()
- )
- return defaults
- def guess_host_provider(self):
- """ Guess the host provider
- Returns:
- dict: The generated default facts for the detected provider
- """
- # TODO: cloud provider facts should probably be submitted upstream
- product_name = self.system_facts['ansible_product_name']
- product_version = self.system_facts['ansible_product_version']
- virt_type = self.system_facts['ansible_virtualization_type']
- virt_role = self.system_facts['ansible_virtualization_role']
- provider = None
- metadata = None
- # TODO: this is not exposed through module_utils/facts.py in ansible,
- # need to create PR for ansible to expose it
- bios_vendor = get_file_content(
- '/sys/devices/virtual/dmi/id/bios_vendor'
- )
- if bios_vendor == 'Google':
- provider = 'gce'
- metadata_url = ('http://metadata.google.internal/'
- 'computeMetadata/v1/?recursive=true')
- headers = {'Metadata-Flavor': 'Google'}
- metadata = get_provider_metadata(metadata_url, True, headers,
- True)
- # Filter sshKeys and serviceAccounts from gce metadata
- if metadata:
- metadata['project']['attributes'].pop('sshKeys', None)
- metadata['instance'].pop('serviceAccounts', None)
- elif (virt_type == 'xen' and virt_role == 'guest'
- and re.match(r'.*\.amazon$', product_version)):
- provider = 'aws'
- metadata_url = 'http://169.254.169.254/latest/meta-data/'
- metadata = get_provider_metadata(metadata_url)
- elif re.search(r'OpenStack', product_name):
- provider = 'openstack'
- metadata_url = ('http://169.254.169.254/openstack/latest/'
- 'meta_data.json')
- metadata = get_provider_metadata(metadata_url, True, None,
- True)
- if metadata:
- ec2_compat_url = 'http://169.254.169.254/latest/meta-data/'
- metadata['ec2_compat'] = get_provider_metadata(
- ec2_compat_url
- )
- # disable pylint maybe-no-member because overloaded use of
- # the module name causes pylint to not detect that results
- # is an array or hash
- # pylint: disable=maybe-no-member
- # Filter public_keys and random_seed from openstack metadata
- metadata.pop('public_keys', None)
- metadata.pop('random_seed', None)
- if not metadata['ec2_compat']:
- metadata = None
- return dict(name=provider, metadata=metadata)
- def init_provider_facts(self):
- """ Initialize the provider facts
- Returns:
- dict: The normalized provider facts
- """
- provider_info = self.guess_host_provider()
- provider_facts = normalize_provider_facts(
- provider_info.get('name'),
- provider_info.get('metadata')
- )
- return provider_facts
- @staticmethod
- def split_openshift_env_fact_keys(openshift_env_fact, openshift_env_structures):
- """ Split openshift_env facts based on openshift_env structures.
- Args:
- openshift_env_fact (string): the openshift_env fact to split
- ex: 'openshift_cloudprovider_openstack_auth_url'
- openshift_env_structures (list): a list of structures to determine fact keys
- ex: ['openshift.cloudprovider.openstack.*']
- Returns:
- list: a list of keys that represent the fact
- ex: ['openshift', 'cloudprovider', 'openstack', 'auth_url']
- """
- # By default, we'll split an openshift_env fact by underscores.
- fact_keys = openshift_env_fact.split('_')
- # Determine if any of the provided variable structures match the fact.
- matching_structure = None
- if openshift_env_structures != None:
- for structure in openshift_env_structures:
- if re.match(structure, openshift_env_fact):
- matching_structure = structure
- # Fact didn't match any variable structures so return the default fact keys.
- if matching_structure is None:
- return fact_keys
- final_keys = []
- structure_keys = matching_structure.split('.')
- for structure_key in structure_keys:
- # Matched current key. Add to final keys.
- if structure_key == fact_keys[structure_keys.index(structure_key)]:
- final_keys.append(structure_key)
- # Wildcard means we will be taking everything from here to the end of the fact.
- elif structure_key == '*':
- final_keys.append('_'.join(fact_keys[structure_keys.index(structure_key):]))
- # Shouldn't have gotten here, return the fact keys.
- else:
- return fact_keys
- return final_keys
- # Disabling too-many-branches and too-many-locals.
- # This should be cleaned up as a TODO item.
- #pylint: disable=too-many-branches, too-many-locals
- def init_local_facts(self, facts=None,
- additive_facts_to_overwrite=None,
- openshift_env=None,
- openshift_env_structures=None,
- protected_facts_to_overwrite=None):
- """ Initialize the local facts
- Args:
- facts (dict): local facts to set
- additive_facts_to_overwrite (list): additive facts to overwrite in jinja
- '.' notation ex: ['master.named_certificates']
- openshift_env (dict): openshift env facts to set
- protected_facts_to_overwrite (list): protected facts to overwrite in jinja
- '.' notation ex: ['master.master_count']
- Returns:
- dict: The result of merging the provided facts with existing
- local facts
- """
- changed = False
- facts_to_set = dict()
- if facts is not None:
- facts_to_set[self.role] = facts
- if openshift_env != {} and openshift_env != None:
- for fact, value in openshift_env.iteritems():
- oo_env_facts = dict()
- current_level = oo_env_facts
- keys = self.split_openshift_env_fact_keys(fact, openshift_env_structures)[1:]
- if len(keys) > 0 and keys[0] != self.role:
- continue
- for key in keys:
- if key == keys[-1]:
- current_level[key] = value
- elif key not in current_level:
- current_level[key] = dict()
- current_level = current_level[key]
- facts_to_set = merge_facts(orig=facts_to_set,
- new=oo_env_facts,
- additive_facts_to_overwrite=[],
- protected_facts_to_overwrite=[])
- local_facts = get_local_facts_from_file(self.filename)
- migrated_facts = migrate_local_facts(local_facts)
- new_local_facts = merge_facts(migrated_facts,
- facts_to_set,
- additive_facts_to_overwrite,
- protected_facts_to_overwrite)
- if 'docker' in new_local_facts:
- # remove duplicate and empty strings from registry lists
- for cat in ['additional', 'blocked', 'insecure']:
- key = '{0}_registries'.format(cat)
- if key in new_local_facts['docker']:
- val = new_local_facts['docker'][key]
- if isinstance(val, basestring):
- val = [x.strip() for x in val.split(',')]
- new_local_facts['docker'][key] = list(set(val) - set(['']))
- # Convert legacy log_options comma sep string to a list if present:
- if 'log_options' in new_local_facts['docker'] and \
- isinstance(new_local_facts['docker']['log_options'], basestring):
- new_local_facts['docker']['log_options'] = new_local_facts['docker']['log_options'].split(',')
- new_local_facts = self.remove_empty_facts(new_local_facts)
- if new_local_facts != local_facts:
- self.validate_local_facts(new_local_facts)
- changed = True
- if not module.check_mode:
- save_local_facts(self.filename, new_local_facts)
- self.changed = changed
- return new_local_facts
- def remove_empty_facts(self, facts=None):
- """ Remove empty facts
- Args:
- facts (dict): facts to clean
- """
- facts_to_remove = []
- for fact, value in facts.iteritems():
- if isinstance(facts[fact], dict):
- facts[fact] = self.remove_empty_facts(facts[fact])
- else:
- if value == "" or value == [""] or value is None:
- facts_to_remove.append(fact)
- for fact in facts_to_remove:
- del facts[fact]
- return facts
- def validate_local_facts(self, facts=None):
- """ Validate local facts
- Args:
- facts (dict): local facts to validate
- """
- invalid_facts = dict()
- invalid_facts = self.validate_master_facts(facts, invalid_facts)
- if invalid_facts:
- msg = 'Invalid facts detected:\n'
- for key in invalid_facts.keys():
- msg += '{0}: {1}\n'.format(key, invalid_facts[key])
- module.fail_json(msg=msg,
- changed=self.changed)
- # disabling pylint errors for line-too-long since we're dealing
- # with best effort reduction of error messages here.
- # disabling errors for too-many-branches since we require checking
- # many conditions.
- # pylint: disable=line-too-long, too-many-branches
- @staticmethod
- def validate_master_facts(facts, invalid_facts):
- """ Validate master facts
- Args:
- facts (dict): local facts to validate
- invalid_facts (dict): collected invalid_facts
- Returns:
- dict: Invalid facts
- """
- if 'master' in facts:
- # openshift.master.session_auth_secrets
- if 'session_auth_secrets' in facts['master']:
- session_auth_secrets = facts['master']['session_auth_secrets']
- if not issubclass(type(session_auth_secrets), list):
- invalid_facts['session_auth_secrets'] = 'Expects session_auth_secrets is a list.'
- elif 'session_encryption_secrets' not in facts['master']:
- invalid_facts['session_auth_secrets'] = ('openshift_master_session_encryption secrets must be set '
- 'if openshift_master_session_auth_secrets is provided.')
- elif len(session_auth_secrets) != len(facts['master']['session_encryption_secrets']):
- invalid_facts['session_auth_secrets'] = ('openshift_master_session_auth_secrets and '
- 'openshift_master_session_encryption_secrets must be '
- 'equal length.')
- else:
- for secret in session_auth_secrets:
- if len(secret) < 32:
- invalid_facts['session_auth_secrets'] = ('Invalid secret in session_auth_secrets. '
- 'Secrets must be at least 32 characters in length.')
- # openshift.master.session_encryption_secrets
- if 'session_encryption_secrets' in facts['master']:
- session_encryption_secrets = facts['master']['session_encryption_secrets']
- if not issubclass(type(session_encryption_secrets), list):
- invalid_facts['session_encryption_secrets'] = 'Expects session_encryption_secrets is a list.'
- elif 'session_auth_secrets' not in facts['master']:
- invalid_facts['session_encryption_secrets'] = ('openshift_master_session_auth_secrets must be '
- 'set if openshift_master_session_encryption_secrets '
- 'is provided.')
- else:
- for secret in session_encryption_secrets:
- if len(secret) not in [16, 24, 32]:
- invalid_facts['session_encryption_secrets'] = ('Invalid secret in session_encryption_secrets. '
- 'Secrets must be 16, 24, or 32 characters in length.')
- return invalid_facts
- def main():
- """ main """
- # disabling pylint errors for global-variable-undefined and invalid-name
- # for 'global module' usage, since it is required to use ansible_facts
- # pylint: disable=global-variable-undefined, invalid-name
- global module
- module = AnsibleModule(
- argument_spec=dict(
- role=dict(default='common', required=False,
- choices=OpenShiftFacts.known_roles),
- local_facts=dict(default=None, type='dict', required=False),
- additive_facts_to_overwrite=dict(default=[], type='list', required=False),
- openshift_env=dict(default={}, type='dict', required=False),
- openshift_env_structures=dict(default=[], type='list', required=False),
- protected_facts_to_overwrite=dict(default=[], type='list', required=False)
- ),
- supports_check_mode=True,
- add_file_common_args=True,
- )
- module.params['gather_subset'] = ['hardware', 'network', 'virtual', 'facter']
- module.params['gather_timeout'] = 10
- module.params['filter'] = '*'
- role = module.params['role']
- local_facts = module.params['local_facts']
- additive_facts_to_overwrite = module.params['additive_facts_to_overwrite']
- openshift_env = module.params['openshift_env']
- openshift_env_structures = module.params['openshift_env_structures']
- protected_facts_to_overwrite = module.params['protected_facts_to_overwrite']
- fact_file = '/etc/ansible/facts.d/openshift.fact'
- openshift_facts = OpenShiftFacts(role,
- fact_file,
- local_facts,
- additive_facts_to_overwrite,
- openshift_env,
- openshift_env_structures,
- protected_facts_to_overwrite)
- file_params = module.params.copy()
- file_params['path'] = fact_file
- file_args = module.load_file_common_arguments(file_params)
- changed = module.set_fs_attributes_if_different(file_args,
- openshift_facts.changed)
- return module.exit_json(changed=changed,
- ansible_facts=openshift_facts.facts)
- # ignore pylint errors related to the module_utils import
- # pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import
- # import module snippets
- from ansible.module_utils.basic import *
- from ansible.module_utils.facts import *
- from ansible.module_utils.urls import *
- if __name__ == '__main__':
- main()
|