diff options
Diffstat (limited to 'roles')
137 files changed, 4524 insertions, 522 deletions
diff --git a/roles/calico/README.md b/roles/calico/README.md index 65f66ebfa..10e5e4e0a 100644 --- a/roles/calico/README.md +++ b/roles/calico/README.md @@ -32,7 +32,6 @@ Additional parameters that can be defined in the inventory are: | Environment | Description | Schema | Default | |---------|----------------------|---------|---------| -|CALICO_IPV4POOL_CIDR| The IPv4 Pool to create if none exists at start up. It is invalid to define this variable and NO_DEFAULT_POOLS. |IPv4 CIDR | 192.168.0.0/16 | | CALICO_IPV4POOL_IPIP | IPIP Mode to use for the IPv4 POOL created at start up. | off, always, cross-subnet | always | | CALICO_LOG_DIR | Directory on the host machine where Calico Logs are written.| String | /var/log/calico | diff --git a/roles/calico/defaults/main.yaml b/roles/calico/defaults/main.yaml index e7a9db92f..b1907f8cb 100644 --- a/roles/calico/defaults/main.yaml +++ b/roles/calico/defaults/main.yaml @@ -1,5 +1,5 @@ --- -kubeconfig: "{{openshift.common.config_base}}/node/{{ 'system:node:' + openshift.common.hostname }}.kubeconfig" +kubeconfig: "{{ openshift.common.config_base }}/node/{{ 'system:node:' + openshift.common.hostname }}.kubeconfig" cni_conf_dir: "/etc/cni/net.d/" cni_bin_dir: "/opt/cni/bin/" @@ -9,7 +9,6 @@ calico_url_cni: "https://github.com/projectcalico/cni-plugin/releases/download/v calico_url_ipam: "https://github.com/projectcalico/cni-plugin/releases/download/v1.10.0/calico-ipam" calico_ipv4pool_ipip: "always" -calico_ipv4pool_cidr: "192.168.0.0/16" calico_log_dir: "/var/log/calico" calico_node_image: "calico/node:v2.4.1" diff --git a/roles/calico/meta/main.yml b/roles/calico/meta/main.yml index 102b82bde..816c81369 100644 --- a/roles/calico/meta/main.yml +++ b/roles/calico/meta/main.yml @@ -14,3 +14,4 @@ galaxy_info: - system dependencies: - role: openshift_facts +- role: openshift_master_facts diff --git a/roles/calico/tasks/main.yml b/roles/calico/tasks/main.yml index e62378532..39f730462 100644 --- a/roles/calico/tasks/main.yml +++ b/roles/calico/tasks/main.yml @@ -38,7 +38,7 @@ path: "{{ item }}" with_items: - "{{ calico_etcd_ca_cert_file }}" - - "{{ calico_etcd_cert_file}}" + - "{{ calico_etcd_cert_file }}" - "{{ calico_etcd_key_file }}" - name: Calico Node | Configure Calico service unit file diff --git a/roles/calico/templates/calico.service.j2 b/roles/calico/templates/calico.service.j2 index 302c5f34e..7653e19b1 100644 --- a/roles/calico/templates/calico.service.j2 +++ b/roles/calico/templates/calico.service.j2 @@ -11,13 +11,14 @@ ExecStart=/usr/bin/docker run --net=host --privileged \ -e WAIT_FOR_DATASTORE=true \ -e FELIX_DEFAULTENDPOINTTOHOSTACTION=ACCEPT \ -e CALICO_IPV4POOL_IPIP={{ calico_ipv4pool_ipip }} \ - -e CALICO_IPV4POOL_CIDR={{ calico_ipv4pool_cidr }} \ + -e CALICO_IPV4POOL_CIDR={{ openshift.master.sdn_cluster_network_cidr }} \ -e FELIX_IPV6SUPPORT=false \ -e ETCD_ENDPOINTS={{ calico_etcd_endpoints }} \ -v {{ calico_etcd_cert_dir }}:{{ calico_etcd_cert_dir }} \ -e ETCD_CA_CERT_FILE={{ calico_etcd_ca_cert_file }} \ -e ETCD_CERT_FILE={{ calico_etcd_cert_file }} \ -e ETCD_KEY_FILE={{ calico_etcd_key_file }} \ + -e CLUSTER_TYPE=origin,bgp \ -e NODENAME={{ openshift.common.hostname }} \ -v {{ calico_log_dir }}:/var/log/calico\ -v /lib/modules:/lib/modules \ diff --git a/roles/calico_master/README.md b/roles/calico_master/README.md index 6f5ed0664..88fd44086 100644 --- a/roles/calico_master/README.md +++ b/roles/calico_master/README.md @@ -29,7 +29,6 @@ Additional parameters that can be defined in the inventory are: | Environment | Description | Schema | Default | |---------|----------------------|---------|---------| -|CALICO_IPV4POOL_CIDR| The IPv4 Pool to create if none exists at start up. It is invalid to define this variable and NO_DEFAULT_POOLS. |IPv4 CIDR | 192.168.0.0/16 | | CALICO_IPV4POOL_IPIP | IPIP Mode to use for the IPv4 POOL created at start up. | off, always, cross-subnet | always | | CALICO_LOG_DIR | Directory on the host machine where Calico Logs are written.| String | /var/log/calico | diff --git a/roles/etcd_common/defaults/main.yml b/roles/etcd_common/defaults/main.yml index b1bfa4592..89993f7ea 100644 --- a/roles/etcd_common/defaults/main.yml +++ b/roles/etcd_common/defaults/main.yml @@ -63,3 +63,13 @@ etcd_client_port: 2379 etcd_peer_port: 2380 etcd_url_scheme: http etcd_peer_url_scheme: http + +etcd_initial_cluster_state: new +etcd_initial_cluster_token: etcd-cluster-1 + +etcd_initial_advertise_peer_urls: "{{ etcd_peer_url_scheme }}://{{ etcd_ip }}:{{ etcd_peer_port }}" +etcd_listen_peer_urls: "{{ etcd_peer_url_scheme }}://{{ etcd_ip }}:{{ etcd_peer_port }}" +etcd_advertise_client_urls: "{{ etcd_url_scheme }}://{{ etcd_ip }}:{{ etcd_client_port }}" +etcd_listen_client_urls: "{{ etcd_url_scheme }}://{{ etcd_ip }}:{{ etcd_client_port }}" + +etcd_systemd_dir: "/etc/systemd/system/{{ etcd_service }}.service.d" diff --git a/roles/etcd_common/meta/main.yml b/roles/etcd_common/meta/main.yml index fb9280c9e..dfb1c7a2c 100644 --- a/roles/etcd_common/meta/main.yml +++ b/roles/etcd_common/meta/main.yml @@ -12,5 +12,4 @@ galaxy_info: categories: - cloud - system -dependencies: -- { role: openshift_repos } +dependencies: [] diff --git a/roles/etcd_common/tasks/main.yml b/roles/etcd_common/tasks/main.yml index 6ed87e6c7..f5bcd03ee 100644 --- a/roles/etcd_common/tasks/main.yml +++ b/roles/etcd_common/tasks/main.yml @@ -6,4 +6,4 @@ - name: Include main action task file include: "{{ r_etcd_common_action }}.yml" - when: r_etcd_common_action != "noop" + when: '"noop" not in r_etcd_common_action' diff --git a/roles/etcd_migrate/tasks/add_ttls.yml b/roles/etcd_migrate/tasks/add_ttls.yml new file mode 100644 index 000000000..c10465af9 --- /dev/null +++ b/roles/etcd_migrate/tasks/add_ttls.yml @@ -0,0 +1,33 @@ +--- +# To be executed on first master +- slurp: + src: "{{ openshift.common.config_base }}/master/master-config.yaml" + register: g_master_config_output + +- set_fact: + accessTokenMaxAgeSeconds: "{{ (g_master_config_output.content|b64decode|from_yaml).oauthConfig.tokenConfig.accessTokenMaxAgeSeconds | default(86400) }}" + authroizeTokenMaxAgeSeconds: "{{ (g_master_config_output.content|b64decode|from_yaml).oauthConfig.tokenConfig.authroizeTokenMaxAgeSeconds | default(500) }}" + controllerLeaseTTL: "{{ (g_master_config_output.content|b64decode|from_yaml).controllerLeaseTTL | default(30) }}" +- name: Re-introduce leases (as a replacement for key TTLs) + command: > + oadm migrate etcd-ttl \ + --cert {{ r_etcd_common_master_peer_cert_file }} \ + --key {{ r_etcd_common_master_peer_key_file }} \ + --cacert {{ r_etcd_common_master_peer_ca_file }} \ + --etcd-address 'https://{{ etcd_peer }}:{{ etcd_client_port }}' \ + --ttl-keys-prefix {{ item.keys }} \ + --lease-duration {{ item.ttl }} + environment: + ETCDCTL_API: 3 + PATH: "/usr/local/bin:/var/usrlocal/bin:{{ ansible_env.PATH }}" + with_items: + - keys: "/kubernetes.io/events" + ttl: "1h" + - keys: "/kubernetes.io/masterleases" + ttl: "10s" + - keys: "/openshift.io/oauth/accesstokens" + ttl: "{{ accessTokenMaxAgeSeconds }}s" + - keys: "/openshift.io/oauth/authorizetokens" + ttl: "{{ authroizeTokenMaxAgeSeconds }}s" + - keys: "/openshift.io/leases/controllers" + ttl: "{{ controllerLeaseTTL }}s" diff --git a/roles/etcd_migrate/tasks/check.yml b/roles/etcd_migrate/tasks/check.yml index b66696b55..0804d9e1c 100644 --- a/roles/etcd_migrate/tasks/check.yml +++ b/roles/etcd_migrate/tasks/check.yml @@ -1,7 +1,4 @@ --- -- fail: - msg: "Currently etcd v3 migration is unsupported while we test it more thoroughly" - when: not openshift_enable_unsupported_configurations | default(false) | bool # Check the cluster is healthy - include: check_cluster_health.yml diff --git a/roles/etcd_migrate/tasks/clean_data.yml b/roles/etcd_migrate/tasks/clean_data.yml new file mode 100644 index 000000000..95a0e7c0a --- /dev/null +++ b/roles/etcd_migrate/tasks/clean_data.yml @@ -0,0 +1,5 @@ +--- +- name: Remove member data + file: + path: /var/lib/etcd/member + state: absent diff --git a/roles/etcd_migrate/tasks/main.yml b/roles/etcd_migrate/tasks/main.yml index 409b0b613..e82f6a6b4 100644 --- a/roles/etcd_migrate/tasks/main.yml +++ b/roles/etcd_migrate/tasks/main.yml @@ -1,8 +1,8 @@ --- - name: Fail if invalid r_etcd_migrate_action provided fail: - msg: "etcd_migrate role can only be called with 'check' or 'migrate' or 'configure'" - when: r_etcd_migrate_action not in ['check', 'migrate', 'configure'] + msg: "etcd_migrate role can only be called with 'check', 'migrate', 'configure', 'add_ttls', or 'clean_data'" + when: r_etcd_migrate_action not in ['check', 'migrate', 'configure', 'add_ttls', 'clean_data'] - name: Include main action task file include: "{{ r_etcd_migrate_action }}.yml" diff --git a/roles/etcd_migrate/tasks/migrate.yml b/roles/etcd_migrate/tasks/migrate.yml index b2cf6d20a..173de77f4 100644 --- a/roles/etcd_migrate/tasks/migrate.yml +++ b/roles/etcd_migrate/tasks/migrate.yml @@ -3,62 +3,45 @@ - set_fact: l_etcd_service: "{{ 'etcd_container' if openshift.common.is_containerized else 'etcd' }}" -- name: Disable etcd members - service: - name: "{{ l_etcd_service }}" - state: stopped - -# Should we skip all TTL keys? https://bugzilla.redhat.com/show_bug.cgi?id=1389773 - name: Migrate etcd data command: > etcdctl migrate --data-dir={{ etcd_data_dir }} environment: ETCDCTL_API: 3 register: l_etcdctl_migrate - # TODO(jchaloup): If any of the members fails, we need to restore all members to v2 from the pre-migrate backup - name: Check the etcd v2 data are correctly migrated fail: msg: "Failed to migrate a member" when: "'finished transforming keys' not in l_etcdctl_migrate.stdout and 'no v2 keys to migrate' not in l_etcdctl_migrate.stdout" - - name: Migration message debug: msg: "Etcd migration finished with: {{ l_etcdctl_migrate.stdout }}" - -- name: Enable etcd member - service: +- name: Set ETCD_FORCE_NEW_CLUSTER=true on first etcd host + lineinfile: + line: "ETCD_FORCE_NEW_CLUSTER=true" + dest: /etc/etcd/etcd.conf +- name: Start etcd + systemd: name: "{{ l_etcd_service }}" state: started +- name: Unset ETCD_FORCE_NEW_CLUSTER=true on first etcd host + lineinfile: + line: "ETCD_FORCE_NEW_CLUSTER=true" + dest: /etc/etcd/etcd.conf + state: absent +- name: Restart first etcd host + systemd: + name: "{{ l_etcd_service }}" + state: restarted -- name: Wait for cluster to become healthy after migration +- name: Wait for cluster to become healthy after bringing up first member command: > etcdctl --cert-file {{ etcd_peer_cert_file }} --key-file {{ etcd_peer_key_file }} --ca-file {{ etcd_peer_ca_file }} --endpoint https://{{ etcd_peer }}:{{ etcd_client_port }} cluster-health register: l_etcd_migrate_health until: l_etcd_migrate_health.rc == 0 retries: 3 delay: 30 - run_once: true - -# NOTE: /usr/local/bin may be removed from the PATH by ansible hence why -# it's added to the environment in this task. -- name: Re-introduce leases (as a replacement for key TTLs) - command: > - oadm migrate etcd-ttl \ - --cert {{ r_etcd_common_master_peer_cert_file }} \ - --key {{ r_etcd_common_master_peer_key_file }} \ - --cacert {{ r_etcd_common_master_peer_ca_file }} \ - --etcd-address 'https://{{ etcd_peer }}:{{ etcd_client_port }}' \ - --ttl-keys-prefix {{ item }} \ - --lease-duration 1h - environment: - ETCDCTL_API: 3 - PATH: "/usr/local/bin:/var/usrlocal/bin:{{ ansible_env.PATH }}" - with_items: - - "/kubernetes.io/events" - - "/kubernetes.io/masterleases" - delegate_to: "{{ groups.oo_first_master[0] }}" - run_once: true - set_fact: r_etcd_migrate_success: true diff --git a/roles/lib_openshift/library/oc_adm_ca_server_cert.py b/roles/lib_openshift/library/oc_adm_ca_server_cert.py index 1b73bfd0e..45d7444a4 100644 --- a/roles/lib_openshift/library/oc_adm_ca_server_cert.py +++ b/roles/lib_openshift/library/oc_adm_ca_server_cert.py @@ -1288,13 +1288,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_adm_csr.py b/roles/lib_openshift/library/oc_adm_csr.py new file mode 100644 index 000000000..231857cca --- /dev/null +++ b/roles/lib_openshift/library/oc_adm_csr.py @@ -0,0 +1,1649 @@ +#!/usr/bin/env python +# pylint: disable=missing-docstring +# flake8: noqa: T001 +# ___ ___ _ _ ___ ___ _ _____ ___ ___ +# / __| __| \| | __| _ \ /_\_ _| __| \ +# | (_ | _|| .` | _|| / / _ \| | | _|| |) | +# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____ +# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _| +# | |) | (_) | | .` | (_) || | | _|| |) | | | | +# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_| +# +# Copyright 2016 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# -*- -*- -*- Begin included fragment: lib/import.py -*- -*- -*- +''' + OpenShiftCLI class that wraps the oc commands in a subprocess +''' +# pylint: disable=too-many-lines + +from __future__ import print_function +import atexit +import copy +import json +import os +import re +import shutil +import subprocess +import tempfile +# pylint: disable=import-error +try: + import ruamel.yaml as yaml +except ImportError: + import yaml + +from ansible.module_utils.basic import AnsibleModule + +# -*- -*- -*- End included fragment: lib/import.py -*- -*- -*- + +# -*- -*- -*- Begin included fragment: doc/csr -*- -*- -*- + +DOCUMENTATION = ''' +--- +module: oc_adm_csr +short_description: Module to approve or deny openshift certificate signing requests +description: + - Wrapper around the openshift `oc adm certificate approve|deny <csr>` command. +options: + state: + description: + - approve|deny|list Approve, deny, and list are the only supported states for certificates + required: false + default: present + choices: + - present + aliases: [] + kubeconfig: + description: + - The path for the kubeconfig file to use for authentication + required: false + default: /etc/origin/master/admin.kubeconfig + aliases: [] + debug: + description: + - Turn on debug output. + required: false + default: False + aliases: [] + nodes: + description: + - A list of the names of the nodes in which to accept the certificates + required: false + default: None + aliases: [] + timeout: + description: + - This flag allows for a timeout value when approving nodes. + required: false + default: 30 + aliases: [] + timeout: + description: + - This flag allows for a timeout value when doing node approvals. + - A zero value for the timeout will block until the nodes have been accepted + required: false + default: 30 + aliases: [] + approve_all: + description: + - This flag allows for the module to approve all CSRs that are found. + - This facilitates testing. + required: false + default: False + aliases: [] + service_account: + description: + - This parameter tells the approval process which service account is being used for the requests + required: false + default: node-bootstrapper + aliases: [] +author: +- "Kenny Woodson <kwoodson@redhat.com>" +extends_documentation_fragment: [] +''' + +EXAMPLES = ''' +- name: Approve certificates for node xyz + oc_adm_scr: + nodes: + - xyz + timeout: 300 + +- name: Approve certificates for node xyz + oc_adm_scr: + nodes: + - xyz + timeout: 0 +''' + +# -*- -*- -*- End included fragment: doc/csr -*- -*- -*- + +# -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- + + +class YeditException(Exception): # pragma: no cover + ''' Exception class for Yedit ''' + pass + + +# pylint: disable=too-many-public-methods +class Yedit(object): # pragma: no cover + ''' Class to modify yaml files ''' + re_valid_key = r"(((\[-?\d+\])|([0-9a-zA-Z%s/_-]+)).?)+$" + re_key = r"(?:\[(-?\d+)\])|([0-9a-zA-Z{}/_-]+)" + com_sep = set(['.', '#', '|', ':']) + + # pylint: disable=too-many-arguments + def __init__(self, + filename=None, + content=None, + content_type='yaml', + separator='.', + backup=False): + self.content = content + self._separator = separator + self.filename = filename + self.__yaml_dict = content + self.content_type = content_type + self.backup = backup + self.load(content_type=self.content_type) + if self.__yaml_dict is None: + self.__yaml_dict = {} + + @property + def separator(self): + ''' getter method for separator ''' + return self._separator + + @separator.setter + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep + + @property + def yaml_dict(self): + ''' getter method for yaml_dict ''' + return self.__yaml_dict + + @yaml_dict.setter + def yaml_dict(self, value): + ''' setter method for yaml_dict ''' + self.__yaml_dict = value + + @staticmethod + def parse_key(key, sep='.'): + '''parse the key allowing the appropriate separator''' + common_separators = list(Yedit.com_sep - set([sep])) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) + + @staticmethod + def valid_key(key, sep='.'): + '''validate the incoming key''' + common_separators = list(Yedit.com_sep - set([sep])) + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): + return False + + return True + + @staticmethod + def remove_entry(data, key, sep='.'): + ''' remove data at location key ''' + if key == '' and isinstance(data, dict): + data.clear() + return True + elif key == '' and isinstance(data, list): + del data[:] + return True + + if not (key and Yedit.valid_key(key, sep)) and \ + isinstance(data, (list, dict)): + return None + + key_indexes = Yedit.parse_key(key, sep) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key and isinstance(data, dict): + data = data.get(dict_key) + elif (arr_ind and isinstance(data, list) and + int(arr_ind) <= len(data) - 1): + data = data[int(arr_ind)] + else: + return None + + # process last index for remove + # expected list entry + if key_indexes[-1][0]: + if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501 + del data[int(key_indexes[-1][0])] + return True + + # expected dict entry + elif key_indexes[-1][1]: + if isinstance(data, dict): + del data[key_indexes[-1][1]] + return True + + @staticmethod + def add_entry(data, key, item=None, sep='.'): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a#b + return c + ''' + if key == '': + pass + elif (not (key and Yedit.valid_key(key, sep)) and + isinstance(data, (list, dict))): + return None + + key_indexes = Yedit.parse_key(key, sep) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key: + if isinstance(data, dict) and dict_key in data and data[dict_key]: # noqa: E501 + data = data[dict_key] + continue + + elif data and not isinstance(data, dict): + raise YeditException("Unexpected item type found while going through key " + + "path: {} (at key: {})".format(key, dict_key)) + + data[dict_key] = {} + data = data[dict_key] + + elif (arr_ind and isinstance(data, list) and + int(arr_ind) <= len(data) - 1): + data = data[int(arr_ind)] + else: + raise YeditException("Unexpected item type found while going through key path: {}".format(key)) + + if key == '': + data = item + + # process last index for add + # expected list entry + elif key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501 + data[int(key_indexes[-1][0])] = item + + # expected dict entry + elif key_indexes[-1][1] and isinstance(data, dict): + data[key_indexes[-1][1]] = item + + # didn't add/update to an existing list, nor add/update key to a dict + # so we must have been provided some syntax like a.b.c[<int>] = "data" for a + # non-existent array + else: + raise YeditException("Error adding to object at path: {}".format(key)) + + return data + + @staticmethod + def get_entry(data, key, sep='.'): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a.b + return c + ''' + if key == '': + pass + elif (not (key and Yedit.valid_key(key, sep)) and + isinstance(data, (list, dict))): + return None + + key_indexes = Yedit.parse_key(key, sep) + for arr_ind, dict_key in key_indexes: + if dict_key and isinstance(data, dict): + data = data.get(dict_key) + elif (arr_ind and isinstance(data, list) and + int(arr_ind) <= len(data) - 1): + data = data[int(arr_ind)] + else: + return None + + return data + + @staticmethod + def _write(filename, contents): + ''' Actually write the file contents to disk. This helps with mocking. ''' + + tmp_filename = filename + '.yedit' + + with open(tmp_filename, 'w') as yfd: + yfd.write(contents) + + os.rename(tmp_filename, filename) + + def write(self): + ''' write to file ''' + if not self.filename: + raise YeditException('Please specify a filename.') + + if self.backup and self.file_exists(): + shutil.copy(self.filename, self.filename + '.orig') + + # Try to set format attributes if supported + try: + self.yaml_dict.fa.set_block_style() + except AttributeError: + pass + + # Try to use RoundTripDumper if supported. + try: + Yedit._write(self.filename, yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper)) + except AttributeError: + Yedit._write(self.filename, yaml.safe_dump(self.yaml_dict, default_flow_style=False)) + + return (True, self.yaml_dict) + + def read(self): + ''' read from file ''' + # check if it exists + if self.filename is None or not self.file_exists(): + return None + + contents = None + with open(self.filename) as yfd: + contents = yfd.read() + + return contents + + def file_exists(self): + ''' return whether file exists ''' + if os.path.exists(self.filename): + return True + + return False + + def load(self, content_type='yaml'): + ''' return yaml file ''' + contents = self.read() + + if not contents and not self.content: + return None + + if self.content: + if isinstance(self.content, dict): + self.yaml_dict = self.content + return self.yaml_dict + elif isinstance(self.content, str): + contents = self.content + + # check if it is yaml + try: + if content_type == 'yaml' and contents: + # Try to set format attributes if supported + try: + self.yaml_dict.fa.set_block_style() + except AttributeError: + pass + + # Try to use RoundTripLoader if supported. + try: + self.yaml_dict = yaml.safe_load(contents, yaml.RoundTripLoader) + except AttributeError: + self.yaml_dict = yaml.safe_load(contents) + + # Try to set format attributes if supported + try: + self.yaml_dict.fa.set_block_style() + except AttributeError: + pass + + elif content_type == 'json' and contents: + self.yaml_dict = json.loads(contents) + except yaml.YAMLError as err: + # Error loading yaml or json + raise YeditException('Problem with loading yaml file. {}'.format(err)) + + return self.yaml_dict + + def get(self, key): + ''' get a specified key''' + try: + entry = Yedit.get_entry(self.yaml_dict, key, self.separator) + except KeyError: + entry = None + + return entry + + def pop(self, path, key_or_item): + ''' remove a key, value pair from a dict or an item for a list''' + try: + entry = Yedit.get_entry(self.yaml_dict, path, self.separator) + except KeyError: + entry = None + + if entry is None: + return (False, self.yaml_dict) + + if isinstance(entry, dict): + # AUDIT:maybe-no-member makes sense due to fuzzy types + # pylint: disable=maybe-no-member + if key_or_item in entry: + entry.pop(key_or_item) + return (True, self.yaml_dict) + return (False, self.yaml_dict) + + elif isinstance(entry, list): + # AUDIT:maybe-no-member makes sense due to fuzzy types + # pylint: disable=maybe-no-member + ind = None + try: + ind = entry.index(key_or_item) + except ValueError: + return (False, self.yaml_dict) + + entry.pop(ind) + return (True, self.yaml_dict) + + return (False, self.yaml_dict) + + def delete(self, path): + ''' remove path from a dict''' + try: + entry = Yedit.get_entry(self.yaml_dict, path, self.separator) + except KeyError: + entry = None + + if entry is None: + return (False, self.yaml_dict) + + result = Yedit.remove_entry(self.yaml_dict, path, self.separator) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) + + def exists(self, path, value): + ''' check if value exists at path''' + try: + entry = Yedit.get_entry(self.yaml_dict, path, self.separator) + except KeyError: + entry = None + + if isinstance(entry, list): + if value in entry: + return True + return False + + elif isinstance(entry, dict): + if isinstance(value, dict): + rval = False + for key, val in value.items(): + if entry[key] != val: + rval = False + break + else: + rval = True + return rval + + return value in entry + + return entry == value + + def append(self, path, value): + '''append value to a list''' + try: + entry = Yedit.get_entry(self.yaml_dict, path, self.separator) + except KeyError: + entry = None + + if entry is None: + self.put(path, []) + entry = Yedit.get_entry(self.yaml_dict, path, self.separator) + if not isinstance(entry, list): + return (False, self.yaml_dict) + + # AUDIT:maybe-no-member makes sense due to loading data from + # a serialized format. + # pylint: disable=maybe-no-member + entry.append(value) + return (True, self.yaml_dict) + + # pylint: disable=too-many-arguments + def update(self, path, value, index=None, curr_value=None): + ''' put path, value into a dict ''' + try: + entry = Yedit.get_entry(self.yaml_dict, path, self.separator) + except KeyError: + entry = None + + if isinstance(entry, dict): + # AUDIT:maybe-no-member makes sense due to fuzzy types + # pylint: disable=maybe-no-member + if not isinstance(value, dict): + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) + + entry.update(value) + return (True, self.yaml_dict) + + elif isinstance(entry, list): + # AUDIT:maybe-no-member makes sense due to fuzzy types + # pylint: disable=maybe-no-member + ind = None + if curr_value: + try: + ind = entry.index(curr_value) + except ValueError: + return (False, self.yaml_dict) + + elif index is not None: + ind = index + + if ind is not None and entry[ind] != value: + entry[ind] = value + return (True, self.yaml_dict) + + # see if it exists in the list + try: + ind = entry.index(value) + except ValueError: + # doesn't exist, append it + entry.append(value) + return (True, self.yaml_dict) + + # already exists, return + if ind is not None: + return (False, self.yaml_dict) + return (False, self.yaml_dict) + + def put(self, path, value): + ''' put path, value into a dict ''' + try: + entry = Yedit.get_entry(self.yaml_dict, path, self.separator) + except KeyError: + entry = None + + if entry == value: + return (False, self.yaml_dict) + + # deepcopy didn't work + # Try to use ruamel.yaml and fallback to pyyaml + try: + tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, + default_flow_style=False), + yaml.RoundTripLoader) + except AttributeError: + tmp_copy = copy.deepcopy(self.yaml_dict) + + # set the format attributes if available + try: + tmp_copy.fa.set_block_style() + except AttributeError: + pass + + result = Yedit.add_entry(tmp_copy, path, value, self.separator) + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + + return (False, self.yaml_dict) + + self.yaml_dict = tmp_copy + + return (True, self.yaml_dict) + + def create(self, path, value): + ''' create a yaml file ''' + if not self.file_exists(): + # deepcopy didn't work + # Try to use ruamel.yaml and fallback to pyyaml + try: + tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, + default_flow_style=False), + yaml.RoundTripLoader) + except AttributeError: + tmp_copy = copy.deepcopy(self.yaml_dict) + + # set the format attributes if available + try: + tmp_copy.fa.set_block_style() + except AttributeError: + pass + + result = Yedit.add_entry(tmp_copy, path, value, self.separator) + if result is not None: + self.yaml_dict = tmp_copy + return (True, self.yaml_dict) + + return (False, self.yaml_dict) + + @staticmethod + def get_curr_value(invalue, val_type): + '''return the current value''' + if invalue is None: + return None + + curr_value = invalue + if val_type == 'yaml': + curr_value = yaml.load(invalue) + elif val_type == 'json': + curr_value = json.loads(invalue) + + return curr_value + + @staticmethod + def parse_value(inc_value, vtype=''): + '''determine value type passed''' + true_bools = ['y', 'Y', 'yes', 'Yes', 'YES', 'true', 'True', 'TRUE', + 'on', 'On', 'ON', ] + false_bools = ['n', 'N', 'no', 'No', 'NO', 'false', 'False', 'FALSE', + 'off', 'Off', 'OFF'] + + # It came in as a string but you didn't specify value_type as string + # we will convert to bool if it matches any of the above cases + if isinstance(inc_value, str) and 'bool' in vtype: + if inc_value not in true_bools and inc_value not in false_bools: + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) + elif isinstance(inc_value, bool) and 'str' in vtype: + inc_value = str(inc_value) + + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass + # If vtype is not str then go ahead and attempt to yaml load it. + elif isinstance(inc_value, str) and 'str' not in vtype: + try: + inc_value = yaml.safe_load(inc_value) + except Exception: + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) + + return inc_value + + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + + # pylint: disable=too-many-return-statements,too-many-branches + @staticmethod + def run_ansible(params): + '''perform the idempotent crud operations''' + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] + + if params['src']: + rval = yamlfile.load() + + if yamlfile.yaml_dict is None and state != 'present': + return {'failed': True, + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) + yamlfile.yaml_dict = content + + if params['key']: + rval = yamlfile.get(params['key']) or {} + + return {'changed': False, 'result': rval, 'state': state} + + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) + yamlfile.yaml_dict = content + + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) + else: + rval = yamlfile.delete(params['key']) + + if rval[0] and params['src']: + yamlfile.write() + + return {'changed': rval[0], 'result': rval[1], 'state': state} + + elif state == 'present': + # check if content is different than what is in the file + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) + + # We had no edits to make and the contents are the same + if yamlfile.yaml_dict == content and \ + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} + + yamlfile.yaml_dict = content + + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] + + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] + + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) + + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: + yamlfile.write() + + return {'changed': results['changed'], 'result': results['results'], 'state': state} + + # no edits to make + if params['src']: + # pylint: disable=redefined-variable-type + rval = yamlfile.write() + return {'changed': rval[0], + 'result': rval[1], + 'state': state} + + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} + return {'failed': True, 'msg': 'Unkown state passed'} + +# -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- + +# -*- -*- -*- Begin included fragment: lib/base.py -*- -*- -*- +# pylint: disable=too-many-lines +# noqa: E301,E302,E303,T001 + + +class OpenShiftCLIError(Exception): + '''Exception class for openshiftcli''' + pass + + +ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')] + + +def locate_oc_binary(): + ''' Find and return oc binary file ''' + # https://github.com/openshift/openshift-ansible/issues/3410 + # oc can be in /usr/local/bin in some cases, but that may not + # be in $PATH due to ansible/sudo + paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS + + oc_binary = 'oc' + + # Use shutil.which if it is available, otherwise fallback to a naive path search + try: + which_result = shutil.which(oc_binary, path=os.pathsep.join(paths)) + if which_result is not None: + oc_binary = which_result + except AttributeError: + for path in paths: + if os.path.exists(os.path.join(path, oc_binary)): + oc_binary = os.path.join(path, oc_binary) + break + + return oc_binary + + +# pylint: disable=too-few-public-methods +class OpenShiftCLI(object): + ''' Class to wrap the command line tools ''' + def __init__(self, + namespace, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False, + all_namespaces=False): + ''' Constructor for OpenshiftCLI ''' + self.namespace = namespace + self.verbose = verbose + self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig) + self.all_namespaces = all_namespaces + self.oc_binary = locate_oc_binary() + + # Pylint allows only 5 arguments to be passed. + # pylint: disable=too-many-arguments + def _replace_content(self, resource, rname, content, force=False, sep='.'): + ''' replace the current object with the content ''' + res = self._get(resource, rname) + if not res['results']: + return res + + fname = Utils.create_tmpfile(rname + '-') + + yed = Yedit(fname, res['results'][0], separator=sep) + changes = [] + for key, value in content.items(): + changes.append(yed.put(key, value)) + + if any([change[0] for change in changes]): + yed.write() + + atexit.register(Utils.cleanup, [fname]) + + return self._replace(fname, force) + + return {'returncode': 0, 'updated': False} + + def _replace(self, fname, force=False): + '''replace the current object with oc replace''' + # We are removing the 'resourceVersion' to handle + # a race condition when modifying oc objects + yed = Yedit(fname) + results = yed.delete('metadata.resourceVersion') + if results[0]: + yed.write() + + cmd = ['replace', '-f', fname] + if force: + cmd.append('--force') + return self.openshift_cmd(cmd) + + def _create_from_content(self, rname, content): + '''create a temporary file and then call oc create on it''' + fname = Utils.create_tmpfile(rname + '-') + yed = Yedit(fname, content=content) + yed.write() + + atexit.register(Utils.cleanup, [fname]) + + return self._create(fname) + + def _create(self, fname): + '''call oc create on a filename''' + return self.openshift_cmd(['create', '-f', fname]) + + def _delete(self, resource, name=None, selector=None): + '''call oc delete on a resource''' + cmd = ['delete', resource] + if selector is not None: + cmd.append('--selector={}'.format(selector)) + elif name is not None: + cmd.append(name) + else: + raise OpenShiftCLIError('Either name or selector is required when calling delete.') + + return self.openshift_cmd(cmd) + + def _process(self, template_name, create=False, params=None, template_data=None): # noqa: E501 + '''process a template + + template_name: the name of the template to process + create: whether to send to oc create after processing + params: the parameters for the template + template_data: the incoming template's data; instead of a file + ''' + cmd = ['process'] + if template_data: + cmd.extend(['-f', '-']) + else: + cmd.append(template_name) + if params: + param_str = ["{}={}".format(key, str(value).replace("'", r'"')) for key, value in params.items()] + cmd.append('-v') + cmd.extend(param_str) + + results = self.openshift_cmd(cmd, output=True, input_data=template_data) + + if results['returncode'] != 0 or not create: + return results + + fname = Utils.create_tmpfile(template_name + '-') + yed = Yedit(fname, results['results']) + yed.write() + + atexit.register(Utils.cleanup, [fname]) + + return self.openshift_cmd(['create', '-f', fname]) + + def _get(self, resource, name=None, selector=None): + '''return a resource by name ''' + cmd = ['get', resource] + if selector is not None: + cmd.append('--selector={}'.format(selector)) + elif name is not None: + cmd.append(name) + + cmd.extend(['-o', 'json']) + + rval = self.openshift_cmd(cmd, output=True) + + # Ensure results are retuned in an array + if 'items' in rval: + rval['results'] = rval['items'] + elif not isinstance(rval['results'], list): + rval['results'] = [rval['results']] + + return rval + + def _schedulable(self, node=None, selector=None, schedulable=True): + ''' perform oadm manage-node scheduable ''' + cmd = ['manage-node'] + if node: + cmd.extend(node) + else: + cmd.append('--selector={}'.format(selector)) + + cmd.append('--schedulable={}'.format(schedulable)) + + return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw') # noqa: E501 + + def _list_pods(self, node=None, selector=None, pod_selector=None): + ''' perform oadm list pods + + node: the node in which to list pods + selector: the label selector filter if provided + pod_selector: the pod selector filter if provided + ''' + cmd = ['manage-node'] + if node: + cmd.extend(node) + else: + cmd.append('--selector={}'.format(selector)) + + if pod_selector: + cmd.append('--pod-selector={}'.format(pod_selector)) + + cmd.extend(['--list-pods', '-o', 'json']) + + return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw') + + # pylint: disable=too-many-arguments + def _evacuate(self, node=None, selector=None, pod_selector=None, dry_run=False, grace_period=None, force=False): + ''' perform oadm manage-node evacuate ''' + cmd = ['manage-node'] + if node: + cmd.extend(node) + else: + cmd.append('--selector={}'.format(selector)) + + if dry_run: + cmd.append('--dry-run') + + if pod_selector: + cmd.append('--pod-selector={}'.format(pod_selector)) + + if grace_period: + cmd.append('--grace-period={}'.format(int(grace_period))) + + if force: + cmd.append('--force') + + cmd.append('--evacuate') + + return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw') + + def _version(self): + ''' return the openshift version''' + return self.openshift_cmd(['version'], output=True, output_type='raw') + + def _import_image(self, url=None, name=None, tag=None): + ''' perform image import ''' + cmd = ['import-image'] + + image = '{0}'.format(name) + if tag: + image += ':{0}'.format(tag) + + cmd.append(image) + + if url: + cmd.append('--from={0}/{1}'.format(url, image)) + + cmd.append('-n{0}'.format(self.namespace)) + + cmd.append('--confirm') + return self.openshift_cmd(cmd) + + def _run(self, cmds, input_data): + ''' Actually executes the command. This makes mocking easier. ''' + curr_env = os.environ.copy() + curr_env.update({'KUBECONFIG': self.kubeconfig}) + proc = subprocess.Popen(cmds, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=curr_env) + + stdout, stderr = proc.communicate(input_data) + + return proc.returncode, stdout.decode('utf-8'), stderr.decode('utf-8') + + # pylint: disable=too-many-arguments,too-many-branches + def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None): + '''Base command for oc ''' + cmds = [self.oc_binary] + + if oadm: + cmds.append('adm') + + cmds.extend(cmd) + + if self.all_namespaces: + cmds.extend(['--all-namespaces']) + elif self.namespace is not None and self.namespace.lower() not in ['none', 'emtpy']: # E501 + cmds.extend(['-n', self.namespace]) + + if self.verbose: + print(' '.join(cmds)) + + try: + returncode, stdout, stderr = self._run(cmds, input_data) + except OSError as ex: + returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex) + + rval = {"returncode": returncode, + "cmd": ' '.join(cmds)} + + if output_type == 'json': + rval['results'] = {} + if output and stdout: + try: + rval['results'] = json.loads(stdout) + except ValueError as verr: + if "No JSON object could be decoded" in verr.args: + rval['err'] = verr.args + elif output_type == 'raw': + rval['results'] = stdout if output else '' + + if self.verbose: + print("STDOUT: {0}".format(stdout)) + print("STDERR: {0}".format(stderr)) + + if 'err' in rval or returncode != 0: + rval.update({"stderr": stderr, + "stdout": stdout}) + + return rval + + +class Utils(object): # pragma: no cover + ''' utilities for openshiftcli modules ''' + + @staticmethod + def _write(filename, contents): + ''' Actually write the file contents to disk. This helps with mocking. ''' + + with open(filename, 'w') as sfd: + sfd.write(contents) + + @staticmethod + def create_tmp_file_from_contents(rname, data, ftype='yaml'): + ''' create a file in tmp with name and contents''' + + tmp = Utils.create_tmpfile(prefix=rname) + + if ftype == 'yaml': + # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage + # pylint: disable=no-member + if hasattr(yaml, 'RoundTripDumper'): + Utils._write(tmp, yaml.dump(data, Dumper=yaml.RoundTripDumper)) + else: + Utils._write(tmp, yaml.safe_dump(data, default_flow_style=False)) + + elif ftype == 'json': + Utils._write(tmp, json.dumps(data)) + else: + Utils._write(tmp, data) + + # Register cleanup when module is done + atexit.register(Utils.cleanup, [tmp]) + return tmp + + @staticmethod + def create_tmpfile_copy(inc_file): + '''create a temporary copy of a file''' + tmpfile = Utils.create_tmpfile('lib_openshift-') + Utils._write(tmpfile, open(inc_file).read()) + + # Cleanup the tmpfile + atexit.register(Utils.cleanup, [tmpfile]) + + return tmpfile + + @staticmethod + def create_tmpfile(prefix='tmp'): + ''' Generates and returns a temporary file name ''' + + with tempfile.NamedTemporaryFile(prefix=prefix, delete=False) as tmp: + return tmp.name + + @staticmethod + def create_tmp_files_from_contents(content, content_type=None): + '''Turn an array of dict: filename, content into a files array''' + if not isinstance(content, list): + content = [content] + files = [] + for item in content: + path = Utils.create_tmp_file_from_contents(item['path'] + '-', + item['data'], + ftype=content_type) + files.append({'name': os.path.basename(item['path']), + 'path': path}) + return files + + @staticmethod + def cleanup(files): + '''Clean up on exit ''' + for sfile in files: + if os.path.exists(sfile): + if os.path.isdir(sfile): + shutil.rmtree(sfile) + elif os.path.isfile(sfile): + os.remove(sfile) + + @staticmethod + def exists(results, _name): + ''' Check to see if the results include the name ''' + if not results: + return False + + if Utils.find_result(results, _name): + return True + + return False + + @staticmethod + def find_result(results, _name): + ''' Find the specified result by name''' + rval = None + for result in results: + if 'metadata' in result and result['metadata']['name'] == _name: + rval = result + break + + return rval + + @staticmethod + def get_resource_file(sfile, sfile_type='yaml'): + ''' return the service file ''' + contents = None + with open(sfile) as sfd: + contents = sfd.read() + + if sfile_type == 'yaml': + # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage + # pylint: disable=no-member + if hasattr(yaml, 'RoundTripLoader'): + contents = yaml.load(contents, yaml.RoundTripLoader) + else: + contents = yaml.safe_load(contents) + elif sfile_type == 'json': + contents = json.loads(contents) + + return contents + + @staticmethod + def filter_versions(stdout): + ''' filter the oc version output ''' + + version_dict = {} + version_search = ['oc', 'openshift', 'kubernetes'] + + for line in stdout.strip().split('\n'): + for term in version_search: + if not line: + continue + if line.startswith(term): + version_dict[term] = line.split()[-1] + + # horrible hack to get openshift version in Openshift 3.2 + # By default "oc version in 3.2 does not return an "openshift" version + if "openshift" not in version_dict: + version_dict["openshift"] = version_dict["oc"] + + return version_dict + + @staticmethod + def add_custom_versions(versions): + ''' create custom versions strings ''' + + versions_dict = {} + + for tech, version in versions.items(): + # clean up "-" from version + if "-" in version: + version = version.split("-")[0] + + if version.startswith('v'): + versions_dict[tech + '_numeric'] = version[1:].split('+')[0] + # "v3.3.0.33" is what we have, we want "3.3" + versions_dict[tech + '_short'] = version[1:4] + + return versions_dict + + @staticmethod + def openshift_installed(): + ''' check if openshift is installed ''' + import rpm + + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") + + return rpmquery.count() > 0 + + # Disabling too-many-branches. This is a yaml dictionary comparison function + # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements + @staticmethod + def check_def_equal(user_def, result_def, skip_keys=None, debug=False): + ''' Given a user defined definition, compare it with the results given back by our query. ''' + + # Currently these values are autogenerated and we do not need to check them + skip = ['metadata', 'status'] + if skip_keys: + skip.extend(skip_keys) + + for key, value in result_def.items(): + if key in skip: + continue + + # Both are lists + if isinstance(value, list): + if key not in user_def: + if debug: + print('User data does not have key [%s]' % key) + print('User data: %s' % user_def) + return False + + if not isinstance(user_def[key], list): + if debug: + print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key])) + return False + + if len(user_def[key]) != len(value): + if debug: + print("List lengths are not equal.") + print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value))) + print("user_def: %s" % user_def[key]) + print("value: %s" % value) + return False + + for values in zip(user_def[key], value): + if isinstance(values[0], dict) and isinstance(values[1], dict): + if debug: + print('sending list - list') + print(type(values[0])) + print(type(values[1])) + result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug) + if not result: + print('list compare returned false') + return False + + elif value != user_def[key]: + if debug: + print('value should be identical') + print(user_def[key]) + print(value) + return False + + # recurse on a dictionary + elif isinstance(value, dict): + if key not in user_def: + if debug: + print("user_def does not have key [%s]" % key) + return False + if not isinstance(user_def[key], dict): + if debug: + print("dict returned false: not instance of dict") + return False + + # before passing ensure keys match + api_values = set(value.keys()) - set(skip) + user_values = set(user_def[key].keys()) - set(skip) + if api_values != user_values: + if debug: + print("keys are not equal in dict") + print(user_values) + print(api_values) + return False + + result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug) + if not result: + if debug: + print("dict returned false") + print(result) + return False + + # Verify each key, value pair is the same + else: + if key not in user_def or value != user_def[key]: + if debug: + print("value not equal; user_def does not have key") + print(key) + print(value) + if key in user_def: + print(user_def[key]) + return False + + if debug: + print('returning true') + return True + +class OpenShiftCLIConfig(object): + '''Generic Config''' + def __init__(self, rname, namespace, kubeconfig, options): + self.kubeconfig = kubeconfig + self.name = rname + self.namespace = namespace + self._options = options + + @property + def config_options(self): + ''' return config options ''' + return self._options + + def to_option_list(self, ascommalist=''): + '''return all options as a string + if ascommalist is set to the name of a key, and + the value of that key is a dict, format the dict + as a list of comma delimited key=value pairs''' + return self.stringify(ascommalist) + + def stringify(self, ascommalist=''): + ''' return the options hash as cli params in a string + if ascommalist is set to the name of a key, and + the value of that key is a dict, format the dict + as a list of comma delimited key=value pairs ''' + rval = [] + for key in sorted(self.config_options.keys()): + data = self.config_options[key] + if data['include'] \ + and (data['value'] or isinstance(data['value'], int)): + if key == ascommalist: + val = ','.join(['{}={}'.format(kk, vv) for kk, vv in sorted(data['value'].items())]) + else: + val = data['value'] + rval.append('--{}={}'.format(key.replace('_', '-'), val)) + + return rval + + +# -*- -*- -*- End included fragment: lib/base.py -*- -*- -*- + +# -*- -*- -*- Begin included fragment: class/oc_adm_csr.py -*- -*- -*- + + +class OCcsr(OpenShiftCLI): + ''' Class to wrap the oc adm certificate command line''' + kind = 'csr' + + # pylint: disable=too-many-arguments + def __init__(self, + nodes=None, + approve_all=False, + service_account=None, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for oc adm certificate ''' + super(OCcsr, self).__init__(None, kubeconfig, verbose) + self.service_account = service_account + self.nodes = self.create_nodes(nodes) + self._csrs = [] + self.approve_all = approve_all + self.verbose = verbose + + @property + def csrs(self): + '''property for managing csrs''' + # any processing needed?? + self._csrs = self._get(resource=self.kind)['results'][0]['items'] + return self._csrs + + def create_nodes(self, nodes): + '''create a node object to track csr signing status''' + nodes_list = [] + + if nodes is None: + return nodes_list + + results = self._get(resource='nodes')['results'][0]['items'] + + for node in nodes: + nodes_list.append(dict(name=node, csrs={}, accepted=False, denied=False)) + + for ocnode in results: + if node in ocnode['metadata']['name']: + nodes_list[-1]['accepted'] = True + + return nodes_list + + def get(self): + '''get the current certificate signing requests''' + return self.csrs + + @staticmethod + def action_needed(csr, action): + '''check to see if csr is in desired state''' + if csr['status'] == {}: + return True + + state = csr['status']['conditions'][0]['type'] + + if action == 'approve' and state != 'Approved': + return True + + elif action == 'deny' and state != 'Denied': + return True + + return False + + def match_node(self, csr): + '''match an inc csr to a node in self.nodes''' + for node in self.nodes: + # we have a match + if node['name'] in csr['metadata']['name']: + node['csrs'][csr['metadata']['name']] = csr + + # check that the username is the node and type is 'Approved' + if node['name'] in csr['spec']['username'] and csr['status']: + if csr['status']['conditions'][0]['type'] == 'Approved': + node['accepted'] = True + # check type is 'Denied' and mark node as such + if csr['status'] and csr['status']['conditions'][0]['type'] == 'Denied': + node['denied'] = True + + return node + + return None + + def finished(self): + '''determine if there are more csrs to sign''' + # if nodes is set and we have nodes then return if all nodes are 'accepted' + if self.nodes is not None and len(self.nodes) > 0: + return all([node['accepted'] or node['denied'] for node in self.nodes]) + + # we are approving everything or we still have nodes outstanding + return False + + def manage(self, action): + '''run openshift oc adm ca create-server-cert cmd and store results into self.nodes + + we attempt to verify if the node is one that was given to us to accept. + + action - (allow | deny) + ''' + + results = [] + # There are 2 types of requests: + # - node-bootstrapper-client-ip-172-31-51-246-ec2-internal + # The client request allows the client to talk to the api/controller + # - node-bootstrapper-server-ip-172-31-51-246-ec2-internal + # The server request allows the server to join the cluster + # Here we need to determine how to approve/deny + # we should query the csrs and verify they are from the nodes we thought + for csr in self.csrs: + node = self.match_node(csr) + # oc adm certificate <approve|deny> csr + # there are 3 known states: Denied, Aprroved, {} + # verify something is needed by OCcsr.action_needed + # if approve_all, then do it + # if you passed in nodes, you must have a node that matches + if self.approve_all or (node and OCcsr.action_needed(csr, action)): + result = self.openshift_cmd(['certificate', action, csr['metadata']['name']], oadm=True) + # client should have service account name in username field + # server should have node name in username field + if node and csr['metadata']['name'] not in node['csrs']: + node['csrs'][csr['metadata']['name']] = csr + + # accept node in cluster + if node['name'] in csr['spec']['username']: + node['accepted'] = True + + results.append(result) + + return results + + @staticmethod + def run_ansible(params, check_mode=False): + '''run the idempotent ansible code''' + + client = OCcsr(params['nodes'], + params['approve_all'], + params['service_account'], + params['kubeconfig'], + params['debug']) + + state = params['state'] + + api_rval = client.get() + + if state == 'list': + return {'changed': False, 'results': api_rval, 'state': state} + + if state in ['approve', 'deny']: + if check_mode: + return {'changed': True, + 'msg': "CHECK_MODE: Would have {} the certificate.".format(params['state']), + 'state': state} + + all_results = [] + finished = False + timeout = False + import time + # loop for timeout or block until all nodes pass + ctr = 0 + while True: + + all_results.extend(client.manage(params['state'])) + if client.finished(): + finished = True + break + + if params['timeout'] == 0: + if not params['approve_all']: + ctr = 0 + + if ctr * 2 > params['timeout']: + timeout = True + break + + # This provides time for the nodes to send their csr requests between approvals + time.sleep(2) + + ctr += 1 + + for result in all_results: + if result['returncode'] != 0: + return {'failed': True, 'msg': all_results} + + return dict(changed=len(all_results) > 0, + results=all_results, + nodes=client.nodes, + state=state, + finished=finished, + timeout=timeout) + + return {'failed': True, + 'msg': 'Unknown state passed. %s' % state} + + +# -*- -*- -*- End included fragment: class/oc_adm_csr.py -*- -*- -*- + +# -*- -*- -*- Begin included fragment: ansible/oc_adm_csr.py -*- -*- -*- + +def main(): + ''' + ansible oc module for approving certificate signing requests + ''' + + module = AnsibleModule( + argument_spec=dict( + kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), + state=dict(default='approve', type='str', + choices=['approve', 'deny', 'list']), + debug=dict(default=False, type='bool'), + nodes=dict(default=None, type='list'), + timeout=dict(default=30, type='int'), + approve_all=dict(default=False, type='bool'), + service_account=dict(default='node-bootstrapper', type='str'), + ), + supports_check_mode=True, + mutually_exclusive=[['approve_all', 'nodes']], + ) + + if module.params['nodes'] == []: + module.fail_json(**dict(failed=True, msg='Please specify hosts.')) + + rval = OCcsr.run_ansible(module.params, module.check_mode) + + if 'failed' in rval: + return module.fail_json(**rval) + + return module.exit_json(**rval) + + +if __name__ == '__main__': + main() + +# -*- -*- -*- End included fragment: ansible/oc_adm_csr.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_manage_node.py b/roles/lib_openshift/library/oc_adm_manage_node.py index b09321a5b..44f3f57d8 100644 --- a/roles/lib_openshift/library/oc_adm_manage_node.py +++ b/roles/lib_openshift/library/oc_adm_manage_node.py @@ -1274,13 +1274,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_adm_policy_group.py b/roles/lib_openshift/library/oc_adm_policy_group.py index 7154fd839..687cff579 100644 --- a/roles/lib_openshift/library/oc_adm_policy_group.py +++ b/roles/lib_openshift/library/oc_adm_policy_group.py @@ -1260,13 +1260,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_adm_policy_user.py b/roles/lib_openshift/library/oc_adm_policy_user.py index 3fcf49799..ddf5d90b7 100644 --- a/roles/lib_openshift/library/oc_adm_policy_user.py +++ b/roles/lib_openshift/library/oc_adm_policy_user.py @@ -1260,13 +1260,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_adm_registry.py b/roles/lib_openshift/library/oc_adm_registry.py index bf2650460..c00eee381 100644 --- a/roles/lib_openshift/library/oc_adm_registry.py +++ b/roles/lib_openshift/library/oc_adm_registry.py @@ -1378,13 +1378,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_adm_router.py b/roles/lib_openshift/library/oc_adm_router.py index a2b7d12c0..0c925ab0b 100644 --- a/roles/lib_openshift/library/oc_adm_router.py +++ b/roles/lib_openshift/library/oc_adm_router.py @@ -1403,13 +1403,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_clusterrole.py b/roles/lib_openshift/library/oc_clusterrole.py index d101eac1c..567ecfd4e 100644 --- a/roles/lib_openshift/library/oc_clusterrole.py +++ b/roles/lib_openshift/library/oc_clusterrole.py @@ -1252,13 +1252,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_configmap.py b/roles/lib_openshift/library/oc_configmap.py index 7cd29215f..9515de569 100644 --- a/roles/lib_openshift/library/oc_configmap.py +++ b/roles/lib_openshift/library/oc_configmap.py @@ -1258,13 +1258,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_edit.py b/roles/lib_openshift/library/oc_edit.py index 5b11f45ba..d461e5ae9 100644 --- a/roles/lib_openshift/library/oc_edit.py +++ b/roles/lib_openshift/library/oc_edit.py @@ -1302,13 +1302,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_env.py b/roles/lib_openshift/library/oc_env.py index d3834ce0c..22ad58725 100644 --- a/roles/lib_openshift/library/oc_env.py +++ b/roles/lib_openshift/library/oc_env.py @@ -1269,13 +1269,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_group.py b/roles/lib_openshift/library/oc_group.py index 0d751fe28..b6c6e47d9 100644 --- a/roles/lib_openshift/library/oc_group.py +++ b/roles/lib_openshift/library/oc_group.py @@ -1242,13 +1242,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_image.py b/roles/lib_openshift/library/oc_image.py index 3a6ba3e56..f7fc286e0 100644 --- a/roles/lib_openshift/library/oc_image.py +++ b/roles/lib_openshift/library/oc_image.py @@ -1261,13 +1261,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_label.py b/roles/lib_openshift/library/oc_label.py index 5db036b23..2206878a4 100644 --- a/roles/lib_openshift/library/oc_label.py +++ b/roles/lib_openshift/library/oc_label.py @@ -1278,13 +1278,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_obj.py b/roles/lib_openshift/library/oc_obj.py index 9b0c0e0e4..126d7a617 100644 --- a/roles/lib_openshift/library/oc_obj.py +++ b/roles/lib_openshift/library/oc_obj.py @@ -1281,13 +1281,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements @@ -1478,7 +1477,16 @@ class OCObject(OpenShiftCLI): if files: return self._create(files[0]) - content['data'] = yaml.dump(content['data']) + # pylint: disable=no-member + # The purpose of this change is twofold: + # - we need a check to only use the ruamel specific dumper if ruamel is loaded + # - the dumper or the flow style change is needed so openshift is able to parse + # the resulting yaml, at least until gopkg.in/yaml.v2 is updated + if hasattr(yaml, 'RoundTripDumper'): + content['data'] = yaml.dump(content['data'], Dumper=yaml.RoundTripDumper) + else: + content['data'] = yaml.safe_dump(content['data'], default_flow_style=False) + content_file = Utils.create_tmp_files_from_contents(content)[0] return self._create(content_file['path']) diff --git a/roles/lib_openshift/library/oc_objectvalidator.py b/roles/lib_openshift/library/oc_objectvalidator.py index 130521761..d20904d0d 100644 --- a/roles/lib_openshift/library/oc_objectvalidator.py +++ b/roles/lib_openshift/library/oc_objectvalidator.py @@ -1213,13 +1213,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_process.py b/roles/lib_openshift/library/oc_process.py index c6568d520..91199d093 100644 --- a/roles/lib_openshift/library/oc_process.py +++ b/roles/lib_openshift/library/oc_process.py @@ -1270,13 +1270,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_project.py b/roles/lib_openshift/library/oc_project.py index a78bc06d2..f9b2d81fa 100644 --- a/roles/lib_openshift/library/oc_project.py +++ b/roles/lib_openshift/library/oc_project.py @@ -1267,13 +1267,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements @@ -1657,6 +1656,9 @@ class OCProject(OpenShiftCLI): # Create it here api_rval = oadm_project.create() + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + # return the created object api_rval = oadm_project.get() diff --git a/roles/lib_openshift/library/oc_pvc.py b/roles/lib_openshift/library/oc_pvc.py index a21540962..895322ba5 100644 --- a/roles/lib_openshift/library/oc_pvc.py +++ b/roles/lib_openshift/library/oc_pvc.py @@ -1274,13 +1274,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_route.py b/roles/lib_openshift/library/oc_route.py index 0c0bc9386..d6db75e1e 100644 --- a/roles/lib_openshift/library/oc_route.py +++ b/roles/lib_openshift/library/oc_route.py @@ -1312,13 +1312,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_scale.py b/roles/lib_openshift/library/oc_scale.py index f112b6dd0..7130cc5fc 100644 --- a/roles/lib_openshift/library/oc_scale.py +++ b/roles/lib_openshift/library/oc_scale.py @@ -1256,13 +1256,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_secret.py b/roles/lib_openshift/library/oc_secret.py index d762e0c38..0c4b99e30 100644 --- a/roles/lib_openshift/library/oc_secret.py +++ b/roles/lib_openshift/library/oc_secret.py @@ -1308,13 +1308,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_service.py b/roles/lib_openshift/library/oc_service.py index 769b75e15..7ab139e85 100644 --- a/roles/lib_openshift/library/oc_service.py +++ b/roles/lib_openshift/library/oc_service.py @@ -1315,13 +1315,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_serviceaccount.py b/roles/lib_openshift/library/oc_serviceaccount.py index 446987eff..5d539ced4 100644 --- a/roles/lib_openshift/library/oc_serviceaccount.py +++ b/roles/lib_openshift/library/oc_serviceaccount.py @@ -1254,13 +1254,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_serviceaccount_secret.py b/roles/lib_openshift/library/oc_serviceaccount_secret.py index c7eb1986a..97e213f46 100644 --- a/roles/lib_openshift/library/oc_serviceaccount_secret.py +++ b/roles/lib_openshift/library/oc_serviceaccount_secret.py @@ -1254,13 +1254,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_storageclass.py b/roles/lib_openshift/library/oc_storageclass.py index 686119c65..9339a85e5 100644 --- a/roles/lib_openshift/library/oc_storageclass.py +++ b/roles/lib_openshift/library/oc_storageclass.py @@ -1272,13 +1272,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_user.py b/roles/lib_openshift/library/oc_user.py index 3a98693b7..2fa349547 100644 --- a/roles/lib_openshift/library/oc_user.py +++ b/roles/lib_openshift/library/oc_user.py @@ -1314,13 +1314,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_version.py b/roles/lib_openshift/library/oc_version.py index 939261526..55e1054e7 100644 --- a/roles/lib_openshift/library/oc_version.py +++ b/roles/lib_openshift/library/oc_version.py @@ -1226,13 +1226,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/library/oc_volume.py b/roles/lib_openshift/library/oc_volume.py index 41e7d0ab8..63bad57b4 100644 --- a/roles/lib_openshift/library/oc_volume.py +++ b/roles/lib_openshift/library/oc_volume.py @@ -1303,13 +1303,12 @@ class Utils(object): # pragma: no cover @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/src/ansible/oc_adm_csr.py b/roles/lib_openshift/src/ansible/oc_adm_csr.py new file mode 100644 index 000000000..9e43a810b --- /dev/null +++ b/roles/lib_openshift/src/ansible/oc_adm_csr.py @@ -0,0 +1,36 @@ +# pylint: skip-file +# flake8: noqa + +def main(): + ''' + ansible oc module for approving certificate signing requests + ''' + + module = AnsibleModule( + argument_spec=dict( + kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), + state=dict(default='approve', type='str', + choices=['approve', 'deny', 'list']), + debug=dict(default=False, type='bool'), + nodes=dict(default=None, type='list'), + timeout=dict(default=30, type='int'), + approve_all=dict(default=False, type='bool'), + service_account=dict(default='node-bootstrapper', type='str'), + ), + supports_check_mode=True, + mutually_exclusive=[['approve_all', 'nodes']], + ) + + if module.params['nodes'] == []: + module.fail_json(**dict(failed=True, msg='Please specify hosts.')) + + rval = OCcsr.run_ansible(module.params, module.check_mode) + + if 'failed' in rval: + return module.fail_json(**rval) + + return module.exit_json(**rval) + + +if __name__ == '__main__': + main() diff --git a/roles/lib_openshift/src/class/oc_adm_csr.py b/roles/lib_openshift/src/class/oc_adm_csr.py new file mode 100644 index 000000000..ea11c6ca9 --- /dev/null +++ b/roles/lib_openshift/src/class/oc_adm_csr.py @@ -0,0 +1,197 @@ +# pylint: skip-file +# flake8: noqa + + +class OCcsr(OpenShiftCLI): + ''' Class to wrap the oc adm certificate command line''' + kind = 'csr' + + # pylint: disable=too-many-arguments + def __init__(self, + nodes=None, + approve_all=False, + service_account=None, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for oc adm certificate ''' + super(OCcsr, self).__init__(None, kubeconfig, verbose) + self.service_account = service_account + self.nodes = self.create_nodes(nodes) + self._csrs = [] + self.approve_all = approve_all + self.verbose = verbose + + @property + def csrs(self): + '''property for managing csrs''' + # any processing needed?? + self._csrs = self._get(resource=self.kind)['results'][0]['items'] + return self._csrs + + def create_nodes(self, nodes): + '''create a node object to track csr signing status''' + nodes_list = [] + + if nodes is None: + return nodes_list + + results = self._get(resource='nodes')['results'][0]['items'] + + for node in nodes: + nodes_list.append(dict(name=node, csrs={}, accepted=False, denied=False)) + + for ocnode in results: + if node in ocnode['metadata']['name']: + nodes_list[-1]['accepted'] = True + + return nodes_list + + def get(self): + '''get the current certificate signing requests''' + return self.csrs + + @staticmethod + def action_needed(csr, action): + '''check to see if csr is in desired state''' + if csr['status'] == {}: + return True + + state = csr['status']['conditions'][0]['type'] + + if action == 'approve' and state != 'Approved': + return True + + elif action == 'deny' and state != 'Denied': + return True + + return False + + def match_node(self, csr): + '''match an inc csr to a node in self.nodes''' + for node in self.nodes: + # we have a match + if node['name'] in csr['metadata']['name']: + node['csrs'][csr['metadata']['name']] = csr + + # check that the username is the node and type is 'Approved' + if node['name'] in csr['spec']['username'] and csr['status']: + if csr['status']['conditions'][0]['type'] == 'Approved': + node['accepted'] = True + # check type is 'Denied' and mark node as such + if csr['status'] and csr['status']['conditions'][0]['type'] == 'Denied': + node['denied'] = True + + return node + + return None + + def finished(self): + '''determine if there are more csrs to sign''' + # if nodes is set and we have nodes then return if all nodes are 'accepted' + if self.nodes is not None and len(self.nodes) > 0: + return all([node['accepted'] or node['denied'] for node in self.nodes]) + + # we are approving everything or we still have nodes outstanding + return False + + def manage(self, action): + '''run openshift oc adm ca create-server-cert cmd and store results into self.nodes + + we attempt to verify if the node is one that was given to us to accept. + + action - (allow | deny) + ''' + + results = [] + # There are 2 types of requests: + # - node-bootstrapper-client-ip-172-31-51-246-ec2-internal + # The client request allows the client to talk to the api/controller + # - node-bootstrapper-server-ip-172-31-51-246-ec2-internal + # The server request allows the server to join the cluster + # Here we need to determine how to approve/deny + # we should query the csrs and verify they are from the nodes we thought + for csr in self.csrs: + node = self.match_node(csr) + # oc adm certificate <approve|deny> csr + # there are 3 known states: Denied, Aprroved, {} + # verify something is needed by OCcsr.action_needed + # if approve_all, then do it + # if you passed in nodes, you must have a node that matches + if self.approve_all or (node and OCcsr.action_needed(csr, action)): + result = self.openshift_cmd(['certificate', action, csr['metadata']['name']], oadm=True) + # client should have service account name in username field + # server should have node name in username field + if node and csr['metadata']['name'] not in node['csrs']: + node['csrs'][csr['metadata']['name']] = csr + + # accept node in cluster + if node['name'] in csr['spec']['username']: + node['accepted'] = True + + results.append(result) + + return results + + @staticmethod + def run_ansible(params, check_mode=False): + '''run the idempotent ansible code''' + + client = OCcsr(params['nodes'], + params['approve_all'], + params['service_account'], + params['kubeconfig'], + params['debug']) + + state = params['state'] + + api_rval = client.get() + + if state == 'list': + return {'changed': False, 'results': api_rval, 'state': state} + + if state in ['approve', 'deny']: + if check_mode: + return {'changed': True, + 'msg': "CHECK_MODE: Would have {} the certificate.".format(params['state']), + 'state': state} + + all_results = [] + finished = False + timeout = False + import time + # loop for timeout or block until all nodes pass + ctr = 0 + while True: + + all_results.extend(client.manage(params['state'])) + if client.finished(): + finished = True + break + + if params['timeout'] == 0: + if not params['approve_all']: + ctr = 0 + + if ctr * 2 > params['timeout']: + timeout = True + break + + # This provides time for the nodes to send their csr requests between approvals + time.sleep(2) + + ctr += 1 + + for result in all_results: + if result['returncode'] != 0: + return {'failed': True, 'msg': all_results} + + return dict(changed=len(all_results) > 0, + results=all_results, + nodes=client.nodes, + state=state, + finished=finished, + timeout=timeout) + + return {'failed': True, + 'msg': 'Unknown state passed. %s' % state} + diff --git a/roles/lib_openshift/src/class/oc_obj.py b/roles/lib_openshift/src/class/oc_obj.py index 5e423bea9..68f7818e4 100644 --- a/roles/lib_openshift/src/class/oc_obj.py +++ b/roles/lib_openshift/src/class/oc_obj.py @@ -50,7 +50,16 @@ class OCObject(OpenShiftCLI): if files: return self._create(files[0]) - content['data'] = yaml.dump(content['data']) + # pylint: disable=no-member + # The purpose of this change is twofold: + # - we need a check to only use the ruamel specific dumper if ruamel is loaded + # - the dumper or the flow style change is needed so openshift is able to parse + # the resulting yaml, at least until gopkg.in/yaml.v2 is updated + if hasattr(yaml, 'RoundTripDumper'): + content['data'] = yaml.dump(content['data'], Dumper=yaml.RoundTripDumper) + else: + content['data'] = yaml.safe_dump(content['data'], default_flow_style=False) + content_file = Utils.create_tmp_files_from_contents(content)[0] return self._create(content_file['path']) diff --git a/roles/lib_openshift/src/class/oc_project.py b/roles/lib_openshift/src/class/oc_project.py index 9ad8111a8..298597067 100644 --- a/roles/lib_openshift/src/class/oc_project.py +++ b/roles/lib_openshift/src/class/oc_project.py @@ -156,6 +156,9 @@ class OCProject(OpenShiftCLI): # Create it here api_rval = oadm_project.create() + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + # return the created object api_rval = oadm_project.get() diff --git a/roles/lib_openshift/src/doc/csr b/roles/lib_openshift/src/doc/csr new file mode 100644 index 000000000..db72dbda3 --- /dev/null +++ b/roles/lib_openshift/src/doc/csr @@ -0,0 +1,80 @@ +# flake8: noqa +# pylint: skip-file + +DOCUMENTATION = ''' +--- +module: oc_adm_csr +short_description: Module to approve or deny openshift certificate signing requests +description: + - Wrapper around the openshift `oc adm certificate approve|deny <csr>` command. +options: + state: + description: + - approve|deny|list Approve, deny, and list are the only supported states for certificates + required: false + default: present + choices: + - present + aliases: [] + kubeconfig: + description: + - The path for the kubeconfig file to use for authentication + required: false + default: /etc/origin/master/admin.kubeconfig + aliases: [] + debug: + description: + - Turn on debug output. + required: false + default: False + aliases: [] + nodes: + description: + - A list of the names of the nodes in which to accept the certificates + required: false + default: None + aliases: [] + timeout: + description: + - This flag allows for a timeout value when approving nodes. + required: false + default: 30 + aliases: [] + timeout: + description: + - This flag allows for a timeout value when doing node approvals. + - A zero value for the timeout will block until the nodes have been accepted + required: false + default: 30 + aliases: [] + approve_all: + description: + - This flag allows for the module to approve all CSRs that are found. + - This facilitates testing. + required: false + default: False + aliases: [] + service_account: + description: + - This parameter tells the approval process which service account is being used for the requests + required: false + default: node-bootstrapper + aliases: [] +author: +- "Kenny Woodson <kwoodson@redhat.com>" +extends_documentation_fragment: [] +''' + +EXAMPLES = ''' +- name: Approve certificates for node xyz + oc_adm_scr: + nodes: + - xyz + timeout: 300 + +- name: Approve certificates for node xyz + oc_adm_scr: + nodes: + - xyz + timeout: 0 +''' diff --git a/roles/lib_openshift/src/lib/base.py b/roles/lib_openshift/src/lib/base.py index 16770b22d..5a307cdb3 100644 --- a/roles/lib_openshift/src/lib/base.py +++ b/roles/lib_openshift/src/lib/base.py @@ -464,13 +464,12 @@ class Utils(object): @staticmethod def openshift_installed(): ''' check if openshift is installed ''' - import yum + import rpm - yum_base = yum.YumBase() - if yum_base.rpmdb.searchNevra(name='atomic-openshift'): - return True + transaction_set = rpm.TransactionSet() + rpmquery = transaction_set.dbMatch("name", "atomic-openshift") - return False + return rpmquery.count() > 0 # Disabling too-many-branches. This is a yaml dictionary comparison function # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml index e9b6bf261..4636f6b9b 100644 --- a/roles/lib_openshift/src/sources.yml +++ b/roles/lib_openshift/src/sources.yml @@ -9,6 +9,16 @@ oc_adm_ca_server_cert.py: - class/oc_adm_ca_server_cert.py - ansible/oc_adm_ca_server_cert.py +oc_adm_csr.py: +- doc/generated +- doc/license +- lib/import.py +- doc/csr +- ../../lib_utils/src/class/yedit.py +- lib/base.py +- class/oc_adm_csr.py +- ansible/oc_adm_csr.py + oc_adm_manage_node.py: - doc/generated - doc/license diff --git a/roles/lib_openshift/src/test/integration/oc_adm_csr.yml b/roles/lib_openshift/src/test/integration/oc_adm_csr.yml new file mode 100755 index 000000000..cad8e36f5 --- /dev/null +++ b/roles/lib_openshift/src/test/integration/oc_adm_csr.yml @@ -0,0 +1,28 @@ +#!/usr/bin/ansible-playbook --module-path=../../../library/ +# ./oc_adm_csr.yml -M ../../../library -e "cli_master_test=$OPENSHIFT_MASTER +--- +- hosts: masters + gather_facts: no + user: root + tasks: + - name: list csrs + oc_adm_csr: + state: list + register: csrout + + - debug: var=csrout + + - name: list csrs + oc_adm_csr: + state: approve + nodes: + - ip-172-31-51-0-ec2-internal + - ip-172-31-51-246-ec2-internal + - ip-172-31-54-12-ec2-internal + - ip-172-31-58-173-ec2-internal + - ip-172-31-58-212-ec2-internal + - ip-172-31-51-246-ec2-internal + - ip-172-31-54-12-ec2-internal + + register: csrout + - debug: var=csrout diff --git a/roles/lib_utils/library/iam_cert23.py b/roles/lib_utils/library/iam_cert23.py new file mode 100644 index 000000000..07b3d3bdf --- /dev/null +++ b/roles/lib_utils/library/iam_cert23.py @@ -0,0 +1,314 @@ +#!/usr/bin/python +# pylint: skip-file +# flake8: noqa +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: iam_cert +short_description: Manage server certificates for use on ELBs and CloudFront +description: + - Allows for the management of server certificates +version_added: "2.0" +options: + name: + description: + - Name of certificate to add, update or remove. + required: true + new_name: + description: + - When state is present, this will update the name of the cert. + - The cert, key and cert_chain parameters will be ignored if this is defined. + new_path: + description: + - When state is present, this will update the path of the cert. + - The cert, key and cert_chain parameters will be ignored if this is defined. + state: + description: + - Whether to create(or update) or delete certificate. + - If new_path or new_name is defined, specifying present will attempt to make an update these. + required: true + choices: [ "present", "absent" ] + path: + description: + - When creating or updating, specify the desired path of the certificate. + default: "/" + cert_chain: + description: + - The path to, or content of the CA certificate chain in PEM encoded format. + As of 2.4 content is accepted. If the parameter is not a file, it is assumed to be content. + cert: + description: + - The path to, or content of the certificate body in PEM encoded format. + As of 2.4 content is accepted. If the parameter is not a file, it is assumed to be content. + key: + description: + - The path to, or content of the private key in PEM encoded format. + As of 2.4 content is accepted. If the parameter is not a file, it is assumed to be content. + dup_ok: + description: + - By default the module will not upload a certificate that is already uploaded into AWS. + If set to True, it will upload the certificate as long as the name is unique. + default: False + + +requirements: [ "boto" ] +author: Jonathan I. Davila +extends_documentation_fragment: + - aws + - ec2 +''' + +EXAMPLES = ''' +# Basic server certificate upload from local file +- iam_cert: + name: very_ssl + state: present + cert: "{{ lookup('file', 'path/to/cert') }}" + key: "{{ lookup('file', 'path/to/key') }}" + cert_chain: "{{ lookup('file', 'path/to/certchain') }}" + +# Basic server certificate upload +- iam_cert: + name: very_ssl + state: present + cert: path/to/cert + key: path/to/key + cert_chain: path/to/certchain + +# Server certificate upload using key string +- iam_cert: + name: very_ssl + state: present + path: "/a/cert/path/" + cert: body_of_somecert + key: vault_body_of_privcertkey + cert_chain: body_of_myverytrustedchain + +# Basic rename of existing certificate +- iam_cert: + name: very_ssl + new_name: new_very_ssl + state: present + +''' +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ec2 import ec2_argument_spec, get_aws_connection_info, connect_to_aws +import os + +try: + import boto + import boto.iam + import boto.ec2 + HAS_BOTO = True +except ImportError: + HAS_BOTO = False + + +def boto_exception(err): + '''generic error message handler''' + if hasattr(err, 'error_message'): + error = err.error_message + elif hasattr(err, 'message'): + error = err.message + else: + error = '%s: %s' % (Exception, err) + + return error + + +def cert_meta(iam, name): + certificate = iam.get_server_certificate(name).get_server_certificate_result.server_certificate + ocert = certificate.certificate_body + opath = certificate.server_certificate_metadata.path + ocert_id = certificate.server_certificate_metadata.server_certificate_id + upload_date = certificate.server_certificate_metadata.upload_date + exp = certificate.server_certificate_metadata.expiration + arn = certificate.server_certificate_metadata.arn + return opath, ocert, ocert_id, upload_date, exp, arn + + +def dup_check(module, iam, name, new_name, cert, orig_cert_names, orig_cert_bodies, dup_ok): + update = False + + # IAM cert names are case insensitive + names_lower = [n.lower() for n in [name, new_name] if n is not None] + orig_cert_names_lower = [ocn.lower() for ocn in orig_cert_names] + + if any(ct in orig_cert_names_lower for ct in names_lower): + for i_name in names_lower: + if cert is not None: + try: + c_index = orig_cert_names_lower.index(i_name) + except NameError: + continue + else: + # NOTE: remove the carriage return to strictly compare the cert bodies. + slug_cert = cert.replace('\r', '') + slug_orig_cert_bodies = orig_cert_bodies[c_index].replace('\r', '') + if slug_orig_cert_bodies == slug_cert: + update = True + break + elif slug_cert.startswith(slug_orig_cert_bodies): + update = True + break + elif slug_orig_cert_bodies != slug_cert: + module.fail_json(changed=False, msg='A cert with the name %s already exists and' + ' has a different certificate body associated' + ' with it. Certificates cannot have the same name' % orig_cert_names[c_index]) + else: + update = True + break + elif cert in orig_cert_bodies and not dup_ok: + for crt_name, crt_body in zip(orig_cert_names, orig_cert_bodies): + if crt_body == cert: + module.fail_json(changed=False, msg='This certificate already' + ' exists under the name %s' % crt_name) + + return update + + +def cert_action(module, iam, name, cpath, new_name, new_path, state, + cert, key, cert_chain, orig_cert_names, orig_cert_bodies, dup_ok): + if state == 'present': + update = dup_check(module, iam, name, new_name, cert, orig_cert_names, + orig_cert_bodies, dup_ok) + if update: + opath, ocert, ocert_id, upload_date, exp, arn = cert_meta(iam, name) + changed = True + if new_name and new_path: + iam.update_server_cert(name, new_cert_name=new_name, new_path=new_path) + module.exit_json(changed=changed, original_name=name, new_name=new_name, + original_path=opath, new_path=new_path, cert_body=ocert, + upload_date=upload_date, expiration_date=exp, arn=arn) + elif new_name and not new_path: + iam.update_server_cert(name, new_cert_name=new_name) + module.exit_json(changed=changed, original_name=name, new_name=new_name, + cert_path=opath, cert_body=ocert, + upload_date=upload_date, expiration_date=exp, arn=arn) + elif not new_name and new_path: + iam.update_server_cert(name, new_path=new_path) + module.exit_json(changed=changed, name=new_name, + original_path=opath, new_path=new_path, cert_body=ocert, + upload_date=upload_date, expiration_date=exp, arn=arn) + else: + changed = False + module.exit_json(changed=changed, name=name, cert_path=opath, cert_body=ocert, + upload_date=upload_date, expiration_date=exp, arn=arn, + msg='No new path or name specified. No changes made') + else: + changed = True + iam.upload_server_cert(name, cert, key, cert_chain=cert_chain, path=cpath) + opath, ocert, ocert_id, upload_date, exp, arn = cert_meta(iam, name) + module.exit_json(changed=changed, name=name, cert_path=opath, cert_body=ocert, + upload_date=upload_date, expiration_date=exp, arn=arn) + elif state == 'absent': + if name in orig_cert_names: + changed = True + iam.delete_server_cert(name) + module.exit_json(changed=changed, deleted_cert=name) + else: + changed = False + module.exit_json(changed=changed, msg='Certificate with the name %s already absent' % name) + + +def load_data(cert, key, cert_chain): + # if paths are provided rather than lookups read the files and return the contents + if cert and os.path.isfile(cert): + cert = open(cert, 'r').read().rstrip() + if key and os.path.isfile(key): + key = open(key, 'r').read().rstrip() + if cert_chain and os.path.isfile(cert_chain): + cert_chain = open(cert_chain, 'r').read() + return cert, key, cert_chain + + +def main(): + argument_spec = ec2_argument_spec() + argument_spec.update(dict( + state=dict(required=True, choices=['present', 'absent']), + name=dict(), + cert=dict(), + key=dict(no_log=True), + cert_chain=dict(), + new_name=dict(), + path=dict(default='/'), + new_path=dict(), + dup_ok=dict(type='bool') + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[ + ['new_path', 'key'], + ['new_path', 'cert'], + ['new_path', 'cert_chain'], + ['new_name', 'key'], + ['new_name', 'cert'], + ['new_name', 'cert_chain'], + ], + ) + + if not HAS_BOTO: + module.fail_json(msg="Boto is required for this module") + + region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module) + + try: + if region: + iam = connect_to_aws(boto.iam, region, **aws_connect_kwargs) + else: + iam = boto.iam.connection.IAMConnection(**aws_connect_kwargs) + except boto.exception.NoAuthHandlerFound as e: + module.fail_json(msg=str(e)) + + state = module.params.get('state') + name = module.params.get('name') + path = module.params.get('path') + new_name = module.params.get('new_name') + new_path = module.params.get('new_path') + dup_ok = module.params.get('dup_ok') + if state == 'present' and not new_name and not new_path: + cert, key, cert_chain = load_data(cert=module.params.get('cert'), + key=module.params.get('key'), + cert_chain=module.params.get('cert_chain')) + else: + cert = key = cert_chain = None + + orig_cert_names = [ctb['server_certificate_name'] for ctb in + iam.get_all_server_certs().list_server_certificates_result.server_certificate_metadata_list] + orig_cert_bodies = [iam.get_server_certificate(thing).get_server_certificate_result.certificate_body + for thing in orig_cert_names] + if new_name == name: + new_name = None + if new_path == path: + new_path = None + + changed = False + try: + cert_action(module, iam, name, path, new_name, new_path, state, + cert, key, cert_chain, orig_cert_names, orig_cert_bodies, dup_ok) + except boto.exception.BotoServerError as err: + module.fail_json(changed=changed, msg=str(err), debug=[cert, key]) + + +if __name__ == '__main__': + main() diff --git a/roles/lib_utils/library/oo_iam_kms.py b/roles/lib_utils/library/oo_iam_kms.py new file mode 100644 index 000000000..c85745f01 --- /dev/null +++ b/roles/lib_utils/library/oo_iam_kms.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python +''' +ansible module for creating AWS IAM KMS keys +''' +# vim: expandtab:tabstop=4:shiftwidth=4 +# +# AWS IAM KMS ansible module +# +# +# Copyright 2016 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Jenkins environment doesn't have all the required libraries +# pylint: disable=import-error +import time +import boto3 +# Ansible modules need this wildcard import +# pylint: disable=unused-wildcard-import, wildcard-import, redefined-builtin +from ansible.module_utils.basic import AnsibleModule + +AWS_ALIAS_URL = "http://docs.aws.amazon.com/kms/latest/developerguide/programming-aliases.html" + + +class AwsIamKms(object): + ''' + ansible module for AWS IAM KMS + ''' + + def __init__(self): + ''' constructor ''' + self.module = None + self.kms_client = None + self.aliases = None + + @staticmethod + def valid_alias_name(user_alias): + ''' AWS KMS aliases must start with 'alias/' ''' + valid_start = 'alias/' + if user_alias.startswith(valid_start): + return True + + return False + + def get_all_kms_info(self): + '''fetch all kms info and return them + + list_keys doesn't have information regarding aliases + list_aliases doesn't have the full kms arn + + fetch both and join them on the targetKeyId + ''' + aliases = self.kms_client.list_aliases()['Aliases'] + keys = self.kms_client.list_keys()['Keys'] + + for alias in aliases: + for key in keys: + if 'TargetKeyId' in alias and 'KeyId' in key: + if alias['TargetKeyId'] == key['KeyId']: + alias.update(key) + + return aliases + + def get_kms_entry(self, user_alias, alias_list): + ''' return single alias details from list of aliases ''' + for alias in alias_list: + if user_alias == alias.get('AliasName', False): + return alias + + msg = "Did not find alias {}".format(user_alias) + self.module.exit_json(failed=True, results=msg) + + @staticmethod + def exists(user_alias, alias_list): + ''' Check if KMS alias already exists ''' + for alias in alias_list: + if user_alias == alias.get('AliasName'): + return True + + return False + + def main(self): + ''' entry point for module ''' + + self.module = AnsibleModule( + argument_spec=dict( + state=dict(default='list', choices=['list', 'present'], type='str'), + region=dict(default=None, required=True, type='str'), + alias=dict(default=None, type='str'), + # description default cannot be None + description=dict(default='', type='str'), + aws_access_key=dict(default=None, type='str'), + aws_secret_key=dict(default=None, type='str'), + ), + ) + + state = self.module.params['state'] + aws_access_key = self.module.params['aws_access_key'] + aws_secret_key = self.module.params['aws_secret_key'] + if aws_access_key and aws_secret_key: + boto3.setup_default_session(aws_access_key_id=aws_access_key, + aws_secret_access_key=aws_secret_key, + region_name=self.module.params['region']) + else: + boto3.setup_default_session(region_name=self.module.params['region']) + + self.kms_client = boto3.client('kms') + + aliases = self.get_all_kms_info() + + if state == 'list': + if self.module.params['alias'] is not None: + user_kms = self.get_kms_entry(self.module.params['alias'], + aliases) + self.module.exit_json(changed=False, results=user_kms, + state="list") + else: + self.module.exit_json(changed=False, results=aliases, + state="list") + + if state == 'present': + + # early sanity check to make sure the alias name conforms with + # AWS alias name requirements + if not self.valid_alias_name(self.module.params['alias']): + self.module.exit_json(failed=True, changed=False, + results="Alias must start with the prefix " + + "'alias/'. Please see " + AWS_ALIAS_URL, + state='present') + + if not self.exists(self.module.params['alias'], aliases): + # if we didn't find it, create it + response = self.kms_client.create_key(KeyUsage='ENCRYPT_DECRYPT', + Description=self.module.params['description']) + kid = response['KeyMetadata']['KeyId'] + response = self.kms_client.create_alias(AliasName=self.module.params['alias'], + TargetKeyId=kid) + # sleep for a bit so that the KMS data can be queried + time.sleep(10) + # get details for newly created KMS entry + new_alias_list = self.kms_client.list_aliases()['Aliases'] + user_kms = self.get_kms_entry(self.module.params['alias'], + new_alias_list) + + self.module.exit_json(changed=True, results=user_kms, + state='present') + + # already exists, normally we would check whether we need to update it + # but this module isn't written to allow changing the alias name + # or changing whether the key is enabled/disabled + user_kms = self.get_kms_entry(self.module.params['alias'], aliases) + self.module.exit_json(changed=False, results=user_kms, + state="present") + + self.module.exit_json(failed=True, + changed=False, + results='Unknown state passed. %s' % state, + state="unknown") + + +if __name__ == '__main__': + AwsIamKms().main() diff --git a/roles/nuage_node/tasks/iptables.yml b/roles/nuage_node/tasks/iptables.yml index 847c8395d..95ee8643a 100644 --- a/roles/nuage_node/tasks/iptables.yml +++ b/roles/nuage_node/tasks/iptables.yml @@ -15,3 +15,9 @@ when: "'nuage-underlay-overlay' not in iptablesrules.stdout" notify: - save iptable rules + +- name: Allow docker daemon traffic from underlay to overlay + command: /sbin/iptables -t nat -A POSTROUTING ! -s {{ hostvars[groups.oo_first_master.0].openshift.master.sdn_cluster_network_cidr }} -o svc-pat-tap -j MASQUERADE -m comment --comment "nuage-docker-underlay-overlay" + when: "'nuage-docker-underlay-overlay' not in iptablesrules.stdout" + notify: + - save iptable rules diff --git a/roles/nuage_node/tasks/main.yaml b/roles/nuage_node/tasks/main.yaml index 9cd743304..66d6ef4ca 100644 --- a/roles/nuage_node/tasks/main.yaml +++ b/roles/nuage_node/tasks/main.yaml @@ -11,6 +11,10 @@ become: yes lineinfile: dest={{ vrs_config }} regexp=^ACTIVE_CONTROLLER line='ACTIVE_CONTROLLER={{ vsc_active_ip }}' +- name: Set the K8S/OSE Cluster service CIDR + become: yes + lineinfile: dest={{ vrs_config }} regexp=^K8S_SERVICE_IPV4_SUBNET line='K8S_SERVICE_IPV4_SUBNET={{ k8s_cluster_service_cidr }}' + - name: Set the Standby Controller become: yes lineinfile: dest={{ vrs_config }} regexp=^STANDBY_CONTROLLER line='STANDBY_CONTROLLER={{ vsc_standby_ip }}' diff --git a/roles/openshift_ami_prep/defaults/main.yml b/roles/openshift_ami_prep/defaults/main.yml new file mode 100644 index 000000000..2ba6d8eae --- /dev/null +++ b/roles/openshift_ami_prep/defaults/main.yml @@ -0,0 +1,50 @@ +--- + + +r_openshift_ami_prep_packages: +- atomic-openshift-master +- atomic-openshift-node +- atomic-openshift-docker-excluder +- atomic-openshift-sdn-ovs +- openvswitch +- docker +- etcd +#- pcs +- haproxy +- dnsmasq +- ntp +- logrotate +- httpd-tools +- bind +- firewalld +- libselinux-python +- conntrack-tools +- openssl +- cloud-init +- iproute +- python-dbus +- PyYAML +- yum-utils +- python2-boto +- python2-boto3 +- cloud-utils-growpart +# gluster +- glusterfs-fuse +- heketi-client +# nfs +- nfs-utils +- flannel +- bash-completion +# cockpit +- cockpit-ws +- cockpit-system +- cockpit-bridge +- cockpit-docker +# iscsi +- iscsi-initiator-utils +# ceph +- ceph-common +# systemcontainer +# - runc +# - container-selinux +# - atomic diff --git a/roles/openshift_ami_prep/tasks/main.yml b/roles/openshift_ami_prep/tasks/main.yml new file mode 100644 index 000000000..98f7bc0e2 --- /dev/null +++ b/roles/openshift_ami_prep/tasks/main.yml @@ -0,0 +1,42 @@ +--- +- name: install repositories + include: yum_repos.yml + static: yes + +- name: install needed rpm(s) + package: + name: "{{ item }}" + state: present + with_items: "{{ r_openshift_ami_prep_packages }}" + +- name: create the directory for node + file: + state: directory + path: "/etc/systemd/system/{{ r_openshift_ami_prep_node }}.service.d" + +- name: laydown systemd override + copy: + dest: "/etc/systemd/system/{{ r_openshift_ami_prep_node }}.service.d/override.conf" + content: | + [Unit] + After=cloud-init.service + +- name: update the sysconfig to have KUBECONFIG + lineinfile: + dest: "/etc/sysconfig/{{ r_openshift_ami_prep_node }}" + line: "KUBECONFIG=/root/csr_kubeconfig" + regexp: "^KUBECONFIG=.*" + +- name: update the ExecStart to have bootstrap + lineinfile: + dest: "/usr/lib/systemd/system/{{ r_openshift_ami_prep_node }}.service" + line: "{% raw %}ExecStart=/usr/bin/openshift start node --bootstrap --kubeconfig=${KUBECONFIG} $OPTIONS{% endraw %}" + regexp: "^ExecStart=.*" + +- name: systemctl enable origin-node + systemd: + name: "{{ item }}" + enabled: no + with_items: + - "{{ r_openshift_ami_prep_node }}.service" + - "{{ r_openshift_ami_prep_master }}.service" diff --git a/roles/openshift_ami_prep/tasks/yum_repos.yml b/roles/openshift_ami_prep/tasks/yum_repos.yml new file mode 100644 index 000000000..c48c67ac2 --- /dev/null +++ b/roles/openshift_ami_prep/tasks/yum_repos.yml @@ -0,0 +1,14 @@ +--- +- name: Create our install repository + yum_repository: + description: "{{ item.description | default(omit) }}" + name: "{{ item.name }}" + baseurl: "{{ item.baseurl }}" + gpgkey: "{{ item.gpgkey | default(omit)}}" + gpgcheck: "{{ item.gpgcheck | default(1) }}" + sslverify: "{{ item.sslverify | default(1) }}" + sslclientkey: "{{ item.sslclientkey | default(omit) }}" + sslclientcert: "{{ item.sslclientcert | default(omit) }}" + file: "{{ item.file }}" + enabled: "{{ item.enabled }}" + with_items: "{{ r_openshift_ami_prep_yum_repositories }}" diff --git a/roles/openshift_aws_ami_copy/README.md b/roles/openshift_aws_ami_copy/README.md new file mode 100644 index 000000000..111818451 --- /dev/null +++ b/roles/openshift_aws_ami_copy/README.md @@ -0,0 +1,50 @@ +openshift_aws_ami_perms +========= + +Ansible role for copying an AMI + +Requirements +------------ + +Ansible Modules: + + +Role Variables +-------------- + +- openshift_aws_ami_copy_src_ami: source AMI id to copy from +- openshift_aws_ami_copy_region: region where the AMI is found +- openshift_aws_ami_copy_name: name to assign to new AMI +- openshift_aws_ami_copy_kms_arn: AWS IAM KMS arn of the key to use for encryption +- openshift_aws_ami_copy_tags: dict with desired tags +- openshift_aws_ami_copy_wait: wait for the ami copy to achieve available status. This fails due to boto waiters. + +Dependencies +------------ + + +Example Playbook +---------------- +```yaml + - name: copy the ami for encrypted disks + include_role: + name: openshift_aws_ami_copy + vars: + r_openshift_aws_ami_copy_region: us-east-1 + r_openshift_aws_ami_copy_name: myami + r_openshift_aws_ami_copy_src_ami: ami-1234 + r_openshift_aws_ami_copy_kms_arn: arn:xxxx + r_openshift_aws_ami_copy_tags: {} + r_openshift_aws_ami_copy_encrypt: False + +``` + +License +------- + +Apache 2.0 + +Author Information +------------------ + +Openshift diff --git a/roles/openshift_aws_ami_copy/tasks/main.yml b/roles/openshift_aws_ami_copy/tasks/main.yml new file mode 100644 index 000000000..15444c8d0 --- /dev/null +++ b/roles/openshift_aws_ami_copy/tasks/main.yml @@ -0,0 +1,26 @@ +--- +- fail: + msg: "{{ item }} needs to be defined" + when: "{{ item }} is not defined" + with_items: + - r_openshift_aws_ami_copy_src_ami + - r_openshift_aws_ami_copy_name + - r_openshift_aws_ami_copy_region + +- name: "Create copied AMI image and wait: {{ r_openshift_aws_ami_copy_wait | default(False) }}" + ec2_ami_copy: + region: "{{ r_openshift_aws_ami_copy_region }}" + source_region: "{{ r_openshift_aws_ami_copy_region }}" + name: "{{ r_openshift_aws_ami_copy_name }}" + source_image_id: "{{ r_openshift_aws_ami_copy_src_ami }}" + encrypted: "{{ r_openshift_aws_ami_copy_encrypt | default(False) }}" + kms_key_id: "{{ r_openshift_aws_ami_copy_kms_arn | default(omit) }}" + wait: "{{ r_openshift_aws_ami_copy_wait | default(omit) }}" + tags: "{{ r_openshift_aws_ami_copy_tags }}" + register: copy_result + +- debug: var=copy_result + +- name: return AMI ID with setfact - openshift_aws_ami_copy_retval_custom_ami + set_fact: + r_openshift_aws_ami_copy_retval_custom_ami: "{{ copy_result.image_id }}" diff --git a/roles/openshift_aws_elb/README.md b/roles/openshift_aws_elb/README.md new file mode 100644 index 000000000..ecc45fa14 --- /dev/null +++ b/roles/openshift_aws_elb/README.md @@ -0,0 +1,75 @@ +openshift_aws_elb +========= + +Ansible role to provision and manage AWS ELB's for Openshift. + +Requirements +------------ + +Ansible Modules: + +- ec2_elb +- ec2_elb_lb + +python package: + +python-boto + +Role Variables +-------------- + +- r_openshift_aws_elb_instances: instances to put in ELB +- r_openshift_aws_elb_elb_name: name of elb +- r_openshift_aws_elb_security_group_names: list of SGs (by name) that the ELB will belong to +- r_openshift_aws_elb_region: AWS Region +- r_openshift_aws_elb_health_check: definition of the ELB health check. See ansible docs for ec2_elb +```yaml + ping_protocol: tcp + ping_port: 443 + response_timeout: 5 + interval: 30 + unhealthy_threshold: 2 + healthy_threshold: 2 +``` +- r_openshift_aws_elb_listeners: definition of the ELB listeners. See ansible docs for ec2_elb +```yaml +- protocol: tcp + load_balancer_port: 80 + instance_protocol: ssl + instance_port: 443 +- protocol: ssl + load_balancer_port: 443 + instance_protocol: ssl + instance_port: 443 + # ssl certificate required for https or ssl + ssl_certificate_id: "{{ r_openshift_aws_elb_cert_arn }}" +``` + +Dependencies +------------ + + +Example Playbook +---------------- +```yaml +- include_role: + name: openshift_aws_elb + vars: + r_openshift_aws_elb_instances: aws_instances_to_put_in_elb + r_openshift_aws_elb_elb_name: elb_name + r_openshift_aws_elb_security_groups: security_group_names + r_openshift_aws_elb_region: aws_region + r_openshift_aws_elb_health_check: "{{ elb_health_check_definition }}" + r_openshift_aws_elb_listeners: "{{ elb_listeners_definition }}" +``` + + +License +------- + +Apache 2.0 + +Author Information +------------------ + +Openshift diff --git a/roles/openshift_aws_elb/defaults/main.yml b/roles/openshift_aws_elb/defaults/main.yml new file mode 100644 index 000000000..ed5d38079 --- /dev/null +++ b/roles/openshift_aws_elb/defaults/main.yml @@ -0,0 +1,33 @@ +--- +r_openshift_aws_elb_health_check: + ping_protocol: tcp + ping_port: 443 + response_timeout: 5 + interval: 30 + unhealthy_threshold: 2 + healthy_threshold: 2 + +r_openshift_aws_elb_cert_arn: '' + +r_openshift_aws_elb_listeners: + master: + external: + - protocol: tcp + load_balancer_port: 80 + instance_protocol: ssl + instance_port: 443 + - protocol: ssl + load_balancer_port: 443 + instance_protocol: ssl + instance_port: 443 + # ssl certificate required for https or ssl + ssl_certificate_id: "{{ r_openshift_aws_elb_cert_arn }}" + internal: + - protocol: tcp + load_balancer_port: 80 + instance_protocol: tcp + instance_port: 80 + - protocol: tcp + load_balancer_port: 443 + instance_protocol: tcp + instance_port: 443 diff --git a/roles/openshift_aws_elb/meta/main.yml b/roles/openshift_aws_elb/meta/main.yml new file mode 100644 index 000000000..58be652a5 --- /dev/null +++ b/roles/openshift_aws_elb/meta/main.yml @@ -0,0 +1,12 @@ +--- +galaxy_info: + author: OpenShift + description: Openshift ELB provisioning + company: Red Hat, Inc + license: ASL 2.0 + min_ansible_version: 1.2 + platforms: + - name: EL + versions: + - 7 +dependencies: [] diff --git a/roles/openshift_aws_elb/tasks/main.yml b/roles/openshift_aws_elb/tasks/main.yml new file mode 100644 index 000000000..64ec18545 --- /dev/null +++ b/roles/openshift_aws_elb/tasks/main.yml @@ -0,0 +1,57 @@ +--- +- name: fetch the default subnet id + ec2_remote_facts: + region: "{{ r_openshift_aws_elb_region }}" + filters: "{{ r_openshift_aws_elb_instance_filter }}" + register: instancesout + +- name: fetch the default subnet id + ec2_vpc_subnet_facts: + region: "{{ r_openshift_aws_elb_region }}" + filters: + "tag:Name": "{{ r_openshift_aws_elb_subnet_name }}" + register: subnetout + +- name: + debug: + msg: "{{ r_openshift_aws_elb_listeners[r_openshift_aws_elb_type][r_openshift_aws_elb_direction] + if 'master' in r_openshift_aws_elb_type or 'infra' in r_openshift_aws_elb_type + else r_openshift_aws_elb_listeners }}" + +- name: "Create ELB {{ r_openshift_aws_elb_name }}" + ec2_elb_lb: + name: "{{ r_openshift_aws_elb_name }}" + state: present + security_group_names: "{{ r_openshift_aws_elb_security_groups }}" + idle_timeout: "{{ r_openshift_aws_elb_idle_timout }}" + region: "{{ r_openshift_aws_elb_region }}" + subnets: + - "{{ subnetout.subnets[0].id }}" + health_check: "{{ r_openshift_aws_elb_health_check }}" + listeners: "{{ r_openshift_aws_elb_listeners[r_openshift_aws_elb_type][r_openshift_aws_elb_direction] + if 'master' in r_openshift_aws_elb_type or 'infra' in r_openshift_aws_elb_type + else r_openshift_aws_elb_listeners }}" + scheme: "{{ r_openshift_aws_elb_scheme }}" + tags: + KubernetesCluster: "{{ r_openshift_aws_elb_clusterid }}" + register: new_elb + +# It is necessary to ignore_errors here because the instances are not in 'ready' +# state when first added to ELB +- name: "Add instances to ELB {{ r_openshift_aws_elb_name }}" + ec2_elb: + instance_id: "{{ item.id }}" + ec2_elbs: "{{ r_openshift_aws_elb_name }}" + state: present + region: "{{ r_openshift_aws_elb_region }}" + wait: False + with_items: "{{ instancesout.instances }}" + ignore_errors: True + retries: 10 + register: elb_call + until: elb_call|succeeded + +- debug: + msg: "{{ item }}" + with_items: + - "{{ new_elb }}" diff --git a/roles/openshift_aws_iam_kms/README.md b/roles/openshift_aws_iam_kms/README.md new file mode 100644 index 000000000..9468e785c --- /dev/null +++ b/roles/openshift_aws_iam_kms/README.md @@ -0,0 +1,43 @@ +openshift_aws_iam_kms +========= + +Ansible role to create AWS IAM KMS keys for encryption + +Requirements +------------ + +Ansible Modules: + +oo_iam_kms + +Role Variables +-------------- + +- r_openshift_aws_iam_kms_region: AWS region to create KMS key +- r_openshift_aws_iam_kms_alias: Alias name to assign to created KMS key + +Dependencies +------------ + +lib_utils + +Example Playbook +---------------- +```yaml +- include_role: + name: openshift_aws_iam_kms + vars: + r_openshift_aws_iam_kms_region: 'us-east-1' + r_openshift_aws_iam_kms_alias: 'alias/clusterABC_kms' +``` + + +License +------- + +Apache 2.0 + +Author Information +------------------ + +Openshift diff --git a/roles/lib_utils/tasks/main.yml b/roles/openshift_aws_iam_kms/defaults/main.yml index ed97d539c..ed97d539c 100644 --- a/roles/lib_utils/tasks/main.yml +++ b/roles/openshift_aws_iam_kms/defaults/main.yml diff --git a/roles/openshift_aws_iam_kms/meta/main.yml b/roles/openshift_aws_iam_kms/meta/main.yml new file mode 100644 index 000000000..e29aaf96b --- /dev/null +++ b/roles/openshift_aws_iam_kms/meta/main.yml @@ -0,0 +1,13 @@ +--- +galaxy_info: + author: OpenShift + description: AWS IAM KMS setup and management + company: Red Hat, Inc + license: ASL 2.0 + min_ansible_version: 1.2 + platforms: + - name: EL + versions: + - 7 +dependencies: +- lib_utils diff --git a/roles/openshift_aws_iam_kms/tasks/main.yml b/roles/openshift_aws_iam_kms/tasks/main.yml new file mode 100644 index 000000000..b541b466c --- /dev/null +++ b/roles/openshift_aws_iam_kms/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- fail: + msg: "{{ item.name }} needs to be defined." + when: "{{ item.cond }}" + with_items: + - name: "{{ r_openshift_aws_iam_kms_alias }}" + cond: "{{ r_openshift_aws_iam_kms_alias is undefined }}" + - name: "{{ r_openshift_aws_iam_kms_region }}" + cond: "{{ r_openshift_aws_iam_kms_region is undefined }}" + +- name: Create IAM KMS key with alias + oo_iam_kms: + state: present + alias: "{{ r_openshift_aws_iam_kms_alias }}" + region: "{{ r_openshift_aws_iam_kms_region }}" + register: created_kms + +- debug: var=created_kms.results diff --git a/roles/openshift_aws_launch_config/README.md b/roles/openshift_aws_launch_config/README.md new file mode 100644 index 000000000..52b7e83b6 --- /dev/null +++ b/roles/openshift_aws_launch_config/README.md @@ -0,0 +1,72 @@ +openshift_aws_launch_config +========= + +Ansible role to create an AWS launch config for a scale group. + +This includes the AMI, volumes, user_data, etc. + +Requirements +------------ + +Ansible Modules: + + +Role Variables +-------------- +- r_openshift_aws_launch_config_name: "{{ launch_config_name }}" +- r_openshift_aws_launch_config_clusterid: "{{ clusterid }}" +- r_openshift_aws_launch_config_region: "{{ region }}" +- r_openshift_aws_launch_config: "{{ node_group_config }}" +```yaml + master: + instance_type: m4.xlarge + ami: ami-cdeec8b6 # if using an encrypted AMI this will be replaced + volumes: + - device_name: /dev/sdb + volume_size: 100 + device_type: gp2 + delete_on_termination: False + health_check: + period: 60 + type: EC2 + min_size: 3 + max_size: 3 + desired_size: 3 + tags: + host-type: master + sub-host-type: default + wait_for_instances: True +``` +- r_openshift_aws_launch_config_type: compute +- r_openshift_aws_launch_config_custom_image: ami-xxxxx +- r_openshift_aws_launch_config_bootstrap_token: <string of kubeconfig> + +Dependencies +------------ + + +Example Playbook +---------------- +```yaml + - name: create compute nodes config + include_role: + name: openshift_aws_launch_config + vars: + r_openshift_aws_launch_config_name: "{{ launch_config_name }}" + r_openshift_aws_launch_config_clusterid: "{{ clusterid }}" + r_openshift_aws_launch_config_region: "{{ region }}" + r_openshift_aws_launch_config: "{{ node_group_config }}" + r_openshift_aws_launch_config_type: compute + r_openshift_aws_launch_config_custom_image: ami-1234 + r_openshift_aws_launch_config_bootstrap_token: abcd +``` + +License +------- + +Apache 2.0 + +Author Information +------------------ + +Openshift diff --git a/roles/openshift_aws_launch_config/defaults/main.yml b/roles/openshift_aws_launch_config/defaults/main.yml new file mode 100644 index 000000000..ed97d539c --- /dev/null +++ b/roles/openshift_aws_launch_config/defaults/main.yml @@ -0,0 +1 @@ +--- diff --git a/roles/openshift_aws_launch_config/meta/main.yml b/roles/openshift_aws_launch_config/meta/main.yml new file mode 100644 index 000000000..e61670cc2 --- /dev/null +++ b/roles/openshift_aws_launch_config/meta/main.yml @@ -0,0 +1,12 @@ +--- +galaxy_info: + author: OpenShift + description: Openshift AWS VPC creation + company: Red Hat, Inc + license: ASL 2.0 + min_ansible_version: 2.3 + platforms: + - name: EL + versions: + - 7 +dependencies: [] diff --git a/roles/openshift_aws_launch_config/tasks/main.yml b/roles/openshift_aws_launch_config/tasks/main.yml new file mode 100644 index 000000000..437cf1f71 --- /dev/null +++ b/roles/openshift_aws_launch_config/tasks/main.yml @@ -0,0 +1,50 @@ +--- +- name: fail when params are not set + fail: + msg: Please specify the role parameters. + when: + - r_openshift_aws_launch_config_cluseterid is undefined + - r_openshift_aws_launch_config_type is undefined + - r_openshift_aws_launch_config_region is undefined + - r_openshift_aws_launch_config is undefined + +- name: fetch the security groups for launch config + ec2_group_facts: + filters: + group-name: + - "{{ r_openshift_aws_launch_config_clusterid }}" # default sg + - "{{ r_openshift_aws_launch_config_clusterid }}_{{ r_openshift_aws_launch_config_type }}" # node type sg + - "{{ r_openshift_aws_launch_config_clusterid }}_{{ r_openshift_aws_launch_config_type }}_k8s" # node type sg k8s + region: "{{ r_openshift_aws_launch_config_region }}" + register: ec2sgs + +# Create the scale group config +- name: Create the node scale group config + ec2_lc: + name: "{{ r_openshift_aws_launch_config_name }}" + region: "{{ r_openshift_aws_launch_config_region }}" + image_id: "{{ r_openshift_aws_launch_config_custom_image if 'ami-' in r_openshift_aws_launch_config_custom_image else r_openshift_aws_launch_config[r_openshift_aws_launch_config_type].ami }}" + instance_type: "{{ r_openshift_aws_launch_config[r_openshift_aws_launch_config_type].instance_type }}" + security_groups: "{{ ec2sgs.security_groups | map(attribute='group_id')| list }}" + user_data: |- + #cloud-config + {% if r_openshift_aws_launch_config_type != 'master' %} + write_files: + - path: /root/csr_kubeconfig + owner: root:root + permissions: '0640' + content: {{ r_openshift_aws_launch_config_bootstrap_token | default('') | to_yaml }} + - path: /root/openshift_settings + owner: root:root + permissions: '0640' + content: + openshift_type: "{{ r_openshift_aws_launch_config_type }}" + runcmd: + - [ systemctl, enable, atomic-openshift-node] + - [ systemctl, start, atomic-openshift-node] + {% endif %} + key_name: "{{ r_openshift_aws_launch_config.ssh_key_name }}" + ebs_optimized: False + volumes: "{{ r_openshift_aws_launch_config[r_openshift_aws_launch_config_type].volumes }}" + assign_public_ip: True + register: test diff --git a/roles/openshift_aws_launch_config/templates/cloud-init.j2 b/roles/openshift_aws_launch_config/templates/cloud-init.j2 new file mode 100644 index 000000000..1a1e29550 --- /dev/null +++ b/roles/openshift_aws_launch_config/templates/cloud-init.j2 @@ -0,0 +1,9 @@ +{% if r_openshift_aws_launch_config_bootstrap_token is defined and r_openshift_aws_launch_config_bootstrap_token is not '' %} +#cloud-config +write_files: +- path: /root/csr_kubeconfig + owner: root:root + permissions: '0640' + content: |- + {{ r_openshift_aws_launch_config_bootstrap_token }} +{% endif %} diff --git a/roles/openshift_aws_node_group/README.md b/roles/openshift_aws_node_group/README.md new file mode 100644 index 000000000..c32c57bc5 --- /dev/null +++ b/roles/openshift_aws_node_group/README.md @@ -0,0 +1,77 @@ +openshift_aws_node_group +========= + +Ansible role to create an aws node group. + +This includes the security group, launch config, and scale group. + +Requirements +------------ + +Ansible Modules: + + +Role Variables +-------------- +```yaml +- r_openshift_aws_node_group_name: myscalegroup +- r_openshift_aws_node_group_clusterid: myclusterid +- r_openshift_aws_node_group_region: us-east-1 +- r_openshift_aws_node_group_lc_name: launch_config +- r_openshift_aws_node_group_type: master|infra|compute +- r_openshift_aws_node_group_config: "{{ node_group_config }}" +```yaml +master: + instance_type: m4.xlarge + ami: ami-cdeec8b6 # if using an encrypted AMI this will be replaced + volumes: + - device_name: /dev/sdb + volume_size: 100 + device_type: gp2 + delete_on_termination: False + health_check: + period: 60 + type: EC2 + min_size: 3 + max_size: 3 + desired_size: 3 + tags: + host-type: master + sub-host-type: default + wait_for_instances: True +``` +- r_openshift_aws_node_group_subnet_name: "{{ subnet_name }}" + +```yaml +us-east-1a # name of subnet +``` + +Dependencies +------------ + + +Example Playbook +---------------- +```yaml + - name: "create {{ openshift_build_node_type }} node groups" + include_role: + name: openshift_aws_node_group + vars: + r_openshift_aws_node_group_name: "{{ clusterid }} openshift compute" + r_openshift_aws_node_group_lc_name: "{{ launch_config_name }}" + r_openshift_aws_node_group_clusterid: "{{ clusterid }}" + r_openshift_aws_node_group_region: "{{ region }}" + r_openshift_aws_node_group_config: "{{ node_group_config }}" + r_openshift_aws_node_group_type: compute + r_openshift_aws_node_group_subnet_name: "{{ subnet_name }}" +``` + +License +------- + +Apache 2.0 + +Author Information +------------------ + +Openshift diff --git a/roles/openshift_aws_node_group/defaults/main.yml b/roles/openshift_aws_node_group/defaults/main.yml new file mode 100644 index 000000000..44c5116a1 --- /dev/null +++ b/roles/openshift_aws_node_group/defaults/main.yml @@ -0,0 +1,58 @@ +--- +r_openshift_aws_node_group_type: master + +r_openshift_aws_node_group_config: + tags: + clusterid: "{{ r_openshift_aws_node_group_clusterid }}" + master: + instance_type: m4.xlarge + ami: "{{ r_openshift_aws_node_group_ami }}" + volumes: + - device_name: /dev/sdb + volume_size: 100 + device_type: gp2 + delete_on_termination: False + health_check: + period: 60 + type: EC2 + min_size: 3 + max_size: 3 + desired_size: 3 + tags: + host-type: master + sub-host-type: default + wait_for_instances: True + compute: + instance_type: m4.xlarge + ami: "{{ r_openshift_aws_node_group_ami }}" + volumes: + - device_name: /dev/sdb + volume_size: 100 + device_type: gp2 + delete_on_termination: True + health_check: + period: 60 + type: EC2 + min_size: 3 + max_size: 100 + desired_size: 3 + tags: + host-type: node + sub-host-type: compute + infra: + instance_type: m4.xlarge + ami: "{{ r_openshift_aws_node_group_ami }}" + volumes: + - device_name: /dev/sdb + volume_size: 100 + device_type: gp2 + delete_on_termination: True + health_check: + period: 60 + type: EC2 + min_size: 2 + max_size: 20 + desired_size: 2 + tags: + host-type: node + sub-host-type: infra diff --git a/roles/openshift_aws_node_group/tasks/main.yml b/roles/openshift_aws_node_group/tasks/main.yml new file mode 100644 index 000000000..6f5364b03 --- /dev/null +++ b/roles/openshift_aws_node_group/tasks/main.yml @@ -0,0 +1,32 @@ +--- +- name: validate role inputs + fail: + msg: Please pass in the required role variables + when: + - r_openshift_aws_node_group_clusterid is not defined + - r_openshift_aws_node_group_region is not defined + - r_openshift_aws_node_group_subnet_name is not defined + +- name: fetch the subnet to use in scale group + ec2_vpc_subnet_facts: + region: "{{ r_openshift_aws_node_group_region }}" + filters: + "tag:Name": "{{ r_openshift_aws_node_group_subnet_name }}" + register: subnetout + +- name: Create the scale group + ec2_asg: + name: "{{ r_openshift_aws_node_group_name }}" + launch_config_name: "{{ r_openshift_aws_node_group_lc_name }}" + health_check_period: "{{ r_openshift_aws_node_group_config[r_openshift_aws_node_group_type].health_check.period }}" + health_check_type: "{{ r_openshift_aws_node_group_config[r_openshift_aws_node_group_type].health_check.type }}" + min_size: "{{ r_openshift_aws_node_group_config[r_openshift_aws_node_group_type].min_size }}" + max_size: "{{ r_openshift_aws_node_group_config[r_openshift_aws_node_group_type].max_size }}" + desired_capacity: "{{ r_openshift_aws_node_group_config[r_openshift_aws_node_group_type].desired_size }}" + region: "{{ r_openshift_aws_node_group_region }}" + termination_policies: "{{ r_openshift_aws_node_group_config[r_openshift_aws_node_group_type].termination_policy if 'termination_policy' in r_openshift_aws_node_group_config[r_openshift_aws_node_group_type] else omit }}" + load_balancers: "{{ r_openshift_aws_node_group_config[r_openshift_aws_node_group_type].elbs if 'elbs' in r_openshift_aws_node_group_config[r_openshift_aws_node_group_type] else omit }}" + wait_for_instances: "{{ r_openshift_aws_node_group_config[r_openshift_aws_node_group_type].wait_for_instances | default(False)}}" + vpc_zone_identifier: "{{ subnetout.subnets[0].id }}" + tags: + - "{{ r_openshift_aws_node_group_config.tags | combine(r_openshift_aws_node_group_config[r_openshift_aws_node_group_type].tags) }}" diff --git a/roles/openshift_aws_s3/README.md b/roles/openshift_aws_s3/README.md new file mode 100644 index 000000000..afafe61cf --- /dev/null +++ b/roles/openshift_aws_s3/README.md @@ -0,0 +1,43 @@ +openshift_aws_s3 +========= + +Ansible role to create an s3 bucket + +Requirements +------------ + +Ansible Modules: + + +Role Variables +-------------- + +- r_openshift_aws_s3_clusterid: myclusterid +- r_openshift_aws_s3_region: us-east-1 +- r_openshift_aws_s3_mode: create|delete + +Dependencies +------------ + + +Example Playbook +---------------- +```yaml +- name: create an s3 bucket + include_role: + name: openshift_aws_s3 + vars: + r_openshift_aws_s3_clusterid: mycluster + r_openshift_aws_s3_region: us-east-1 + r_openshift_aws_s3_mode: create +``` + +License +------- + +Apache 2.0 + +Author Information +------------------ + +Openshift diff --git a/roles/openshift_aws_s3/tasks/main.yml b/roles/openshift_aws_s3/tasks/main.yml new file mode 100644 index 000000000..46bd781bd --- /dev/null +++ b/roles/openshift_aws_s3/tasks/main.yml @@ -0,0 +1,6 @@ +--- +- name: Create an s3 bucket + s3: + bucket: "{{ r_openshift_aws_s3_clusterid }}" + mode: "{{ r_openshift_aws_s3_mode }}" + region: "{{ r_openshift_aws_s3_region }}" diff --git a/roles/openshift_aws_sg/README.md b/roles/openshift_aws_sg/README.md new file mode 100644 index 000000000..eeb76bbb6 --- /dev/null +++ b/roles/openshift_aws_sg/README.md @@ -0,0 +1,59 @@ +openshift_aws_sg +========= + +Ansible role to create an aws security groups + +Requirements +------------ + +Ansible Modules: + + +Role Variables +-------------- + +- r_openshift_aws_sg_clusterid: myclusterid +- r_openshift_aws_sg_region: us-east-1 +- r_openshift_aws_sg_type: master|infra|compute +```yaml +# defaults/main.yml + default: + name: "{{ r_openshift_aws_sg_clusterid }}" + desc: "{{ r_openshift_aws_sg_clusterid }} default" + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 0.0.0.0/0 + - proto: all + from_port: all + to_port: all + group_name: "{{ r_openshift_aws_sg_clusterid }}" +``` + + +Dependencies +------------ + + +Example Playbook +---------------- +```yaml +- name: create security groups for master + include_role: + name: openshift_aws_sg + vars: + r_openshift_aws_sg_clusterid: mycluster + r_openshift_aws_sg_region: us-east-1 + r_openshift_aws_sg_type: master +``` + +License +------- + +Apache 2.0 + +Author Information +------------------ + +Openshift diff --git a/roles/openshift_aws_sg/defaults/main.yml b/roles/openshift_aws_sg/defaults/main.yml new file mode 100644 index 000000000..9c480d337 --- /dev/null +++ b/roles/openshift_aws_sg/defaults/main.yml @@ -0,0 +1,48 @@ +--- +r_openshift_aws_sg_sg: + default: + name: "{{ r_openshift_aws_sg_clusterid }}" + desc: "{{ r_openshift_aws_sg_clusterid }} default" + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 0.0.0.0/0 + - proto: all + from_port: all + to_port: all + group_name: "{{ r_openshift_aws_sg_clusterid }}" + master: + name: "{{ r_openshift_aws_sg_clusterid }}_master" + desc: "{{ r_openshift_aws_sg_clusterid }} master instances" + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 443 + to_port: 443 + cidr_ip: 0.0.0.0/0 + compute: + name: "{{ r_openshift_aws_sg_clusterid }}_compute" + desc: "{{ r_openshift_aws_sg_clusterid }} compute node instances" + infra: + name: "{{ r_openshift_aws_sg_clusterid }}_infra" + desc: "{{ r_openshift_aws_sg_clusterid }} infra node instances" + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 443 + to_port: 443 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 30000 + to_port: 32000 + cidr_ip: 0.0.0.0/0 + etcd: + name: "{{ r_openshift_aws_sg_clusterid }}_etcd" + desc: "{{ r_openshift_aws_sg_clusterid }} etcd instances" diff --git a/roles/openshift_aws_sg/tasks/main.yml b/roles/openshift_aws_sg/tasks/main.yml new file mode 100644 index 000000000..2294fdcc9 --- /dev/null +++ b/roles/openshift_aws_sg/tasks/main.yml @@ -0,0 +1,53 @@ +--- +- name: Validate role inputs + fail: + msg: Please ensure to pass the correct variables + when: + - r_openshift_aws_sg_region is undefined + - r_openshift_aws_sg_region is undefined + + +- name: Fetch the VPC for vpc.id + ec2_vpc_net_facts: + region: "{{ r_openshift_aws_sg_region }}" + filters: + "tag:Name": "{{ r_openshift_aws_sg_clusterid }}" + register: vpcout + +- name: Create default security group for cluster + ec2_group: + name: "{{ r_openshift_aws_sg_sg.default.name }}" + description: "{{ r_openshift_aws_sg_sg.default.desc }}" + region: "{{ r_openshift_aws_sg_region }}" + vpc_id: "{{ vpcout.vpcs[0].id }}" + rules: "{{ r_openshift_aws_sg_sg.default.rules | default(omit, True)}}" + register: sg_default_created + +- name: create the node group sgs + ec2_group: + name: "{{ item.name}}" + description: "{{ item.desc }}" + rules: "{{ item.rules if 'rules' in item else [] }}" + region: "{{ r_openshift_aws_sg_region }}" + vpc_id: "{{ vpcout.vpcs[0].id }}" + register: sg_create + with_items: + - "{{ r_openshift_aws_sg_sg[r_openshift_aws_sg_type]}}" + +- name: create the k8s sgs for the node group + ec2_group: + name: "{{ item.name }}_k8s" + description: "{{ item.desc }} for k8s" + region: "{{ r_openshift_aws_sg_region }}" + vpc_id: "{{ vpcout.vpcs[0].id }}" + register: k8s_sg_create + with_items: + - "{{ r_openshift_aws_sg_sg[r_openshift_aws_sg_type] }}" + +- name: tag sg groups with proper tags + ec2_tag: + tags: + KubernetesCluster: "{{ r_openshift_aws_sg_clusterid }}" + resource: "{{ item.group_id }}" + region: "{{ r_openshift_aws_sg_region }}" + with_items: "{{ k8s_sg_create.results }}" diff --git a/roles/openshift_aws_ssh_keys/README.md b/roles/openshift_aws_ssh_keys/README.md new file mode 100644 index 000000000..4f8667918 --- /dev/null +++ b/roles/openshift_aws_ssh_keys/README.md @@ -0,0 +1,49 @@ +openshift_aws_ssh_keys +========= + +Ansible role for sshind SSH keys + +Requirements +------------ + +Ansible Modules: + + +Role Variables +-------------- + +- r_openshift_aws_ssh_keys_users: list of dicts of users +- r_openshift_aws_ssh_keys_region: ec2_region to install the keys + +Dependencies +------------ + + +Example Playbook +---------------- +```yaml +users: +- username: user1 + pub_key: <user1 ssh public key> +- username: user2 + pub_key: <user2 ssh public key> + +region: us-east-1 + +- include_role: + name: openshift_aws_ssh_keys + vars: + r_openshift_aws_ssh_keys_users: "{{ users }}" + r_openshift_aws_ssh_keys_region: "{{ region }}" +``` + + +License +------- + +Apache 2.0 + +Author Information +------------------ + +Openshift diff --git a/roles/openshift_aws_ssh_keys/tasks/main.yml b/roles/openshift_aws_ssh_keys/tasks/main.yml new file mode 100644 index 000000000..232cf20ed --- /dev/null +++ b/roles/openshift_aws_ssh_keys/tasks/main.yml @@ -0,0 +1,8 @@ +--- +- name: Add the public keys for the users + ec2_key: + name: "{{ item.key_name }}" + key_material: "{{ item.pub_key }}" + region: "{{ r_openshift_aws_ssh_keys_region }}" + with_items: "{{ r_openshift_aws_ssh_keys_users }}" + no_log: True diff --git a/roles/openshift_aws_vpc/README.md b/roles/openshift_aws_vpc/README.md new file mode 100644 index 000000000..d88cf0581 --- /dev/null +++ b/roles/openshift_aws_vpc/README.md @@ -0,0 +1,62 @@ +openshift_aws_vpc +========= + +Ansible role to create a default AWS VPC + +Requirements +------------ + +Ansible Modules: + + +Role Variables +-------------- + +- r_openshift_aws_vpc_clusterid: "{{ clusterid }}" +- r_openshift_aws_vpc_cidr: 172.31.48.0/20 +- r_openshift_aws_vpc_subnets: "{{ subnets }}" +```yaml + subnets: + us-east-1: # These are us-east-1 region defaults. Ensure this matches your region + - cidr: 172.31.48.0/20 + az: "us-east-1c" + - cidr: 172.31.32.0/20 + az: "us-east-1e" + - cidr: 172.31.16.0/20 + az: "us-east-1a" +``` +- r_openshift_aws_vpc_region: "{{ region }}" +- r_openshift_aws_vpc_tags: dict of tags to apply to vpc +- r_openshift_aws_vpc_name: "{{ vpc_name | default(clusterid) }}" + +Dependencies +------------ + + +Example Playbook +---------------- + +```yaml + - name: create default vpc + include_role: + name: openshift_aws_vpc + vars: + r_openshift_aws_vpc_clusterid: mycluster + r_openshift_aws_vpc_cidr: 172.31.48.0/20 + r_openshift_aws_vpc_subnets: "{{ subnets }}" + r_openshift_aws_vpc_region: us-east-1 + r_openshift_aws_vpc_tags: {} + r_openshift_aws_vpc_name: mycluster + +``` + + +License +------- + +Apache 2.0 + +Author Information +------------------ + +Openshift diff --git a/roles/openshift_aws_vpc/defaults/main.yml b/roles/openshift_aws_vpc/defaults/main.yml new file mode 100644 index 000000000..ed97d539c --- /dev/null +++ b/roles/openshift_aws_vpc/defaults/main.yml @@ -0,0 +1 @@ +--- diff --git a/roles/openshift_aws_vpc/tasks/main.yml b/roles/openshift_aws_vpc/tasks/main.yml new file mode 100644 index 000000000..cfe08dae5 --- /dev/null +++ b/roles/openshift_aws_vpc/tasks/main.yml @@ -0,0 +1,53 @@ +--- +- name: Create AWS VPC + ec2_vpc_net: + state: present + cidr_block: "{{ r_openshift_aws_vpc_cidr }}" + dns_support: True + dns_hostnames: True + region: "{{ r_openshift_aws_vpc_region }}" + name: "{{ r_openshift_aws_vpc_clusterid }}" + tags: + Name: "{{ r_openshift_aws_vpc_clusterid }}" + register: vpc + +- name: Sleep to avoid a race condition when creating the vpc + pause: + seconds: 5 + when: vpc.changed + +- name: assign the vpc igw + ec2_vpc_igw: + region: "{{ r_openshift_aws_vpc_region }}" + vpc_id: "{{ vpc.vpc.id }}" + register: igw + +- name: assign the vpc subnets + ec2_vpc_subnet: + region: "{{ r_openshift_aws_vpc_region }}" + vpc_id: "{{ vpc.vpc.id }}" + cidr: "{{ item.cidr }}" + az: "{{ item.az }}" + resource_tags: + Name: "{{ item.az }}" + with_items: "{{ r_openshift_aws_vpc_subnets[r_openshift_aws_vpc_region] }}" + +- name: Grab the route tables from our VPC + ec2_vpc_route_table_facts: + region: "{{ r_openshift_aws_vpc_region }}" + filters: + vpc-id: "{{ vpc.vpc.id }}" + register: route_table + +- name: update the route table in the vpc + ec2_vpc_route_table: + lookup: id + route_table_id: "{{ route_table.route_tables[0].id }}" + vpc_id: "{{ vpc.vpc.id }}" + region: "{{ r_openshift_aws_vpc_region }}" + tags: + Name: "{{ r_openshift_aws_vpc_name }}" + routes: + - dest: 0.0.0.0/0 + gateway_id: igw + register: route_table_out diff --git a/roles/openshift_ca/meta/main.yml b/roles/openshift_ca/meta/main.yml index 444c5b77e..dfbdf0cc7 100644 --- a/roles/openshift_ca/meta/main.yml +++ b/roles/openshift_ca/meta/main.yml @@ -13,6 +13,5 @@ galaxy_info: - cloud - system dependencies: -- role: openshift_repos - role: openshift_cli - role: openshift_named_certificates diff --git a/roles/openshift_cfme/defaults/main.yml b/roles/openshift_cfme/defaults/main.yml index 27ed57703..393bee1f3 100644 --- a/roles/openshift_cfme/defaults/main.yml +++ b/roles/openshift_cfme/defaults/main.yml @@ -1,6 +1,7 @@ --- -# Namespace for the CFME project -openshift_cfme_project: cfme +# Namespace for the CFME project (Note: changed post-3.6 to use +# reserved 'openshift-' namespace prefix) +openshift_cfme_project: openshift-cfme # Namespace/project description openshift_cfme_project_description: ManageIQ - CloudForms Management Engine # Basic user assigned the `admin` role for the project diff --git a/roles/openshift_cli/library/openshift_container_binary_sync.py b/roles/openshift_cli/library/openshift_container_binary_sync.py index c47203211..b40c49701 100644 --- a/roles/openshift_cli/library/openshift_container_binary_sync.py +++ b/roles/openshift_cli/library/openshift_container_binary_sync.py @@ -133,6 +133,11 @@ class BinarySyncer(object): dest_path = os.path.join(self.bin_dir, binary_name) incoming_checksum = self.module.run_command(['sha256sum', src_path])[1] if not os.path.exists(dest_path) or self.module.run_command(['sha256sum', dest_path])[1] != incoming_checksum: + + # See: https://github.com/openshift/openshift-ansible/issues/4965 + if os.path.islink(dest_path): + os.unlink(dest_path) + self.output.append('Removed old symlink {} before copying binary.'.format(dest_path)) shutil.move(src_path, dest_path) self.output.append("Moved %s to %s." % (src_path, dest_path)) self.changed = True diff --git a/roles/openshift_common/meta/main.yml b/roles/openshift_common/meta/main.yml index f1cf3e161..7cc95d8fa 100644 --- a/roles/openshift_common/meta/main.yml +++ b/roles/openshift_common/meta/main.yml @@ -13,4 +13,3 @@ galaxy_info: - cloud dependencies: - role: openshift_facts -- role: openshift_repos diff --git a/roles/openshift_health_checker/library/aos_version.py b/roles/openshift_health_checker/library/aos_version.py index f9babebb9..c8769b511 100644 --- a/roles/openshift_health_checker/library/aos_version.py +++ b/roles/openshift_health_checker/library/aos_version.py @@ -24,11 +24,19 @@ from ansible.module_utils.basic import AnsibleModule # Python 3, we use six for cross compatibility in this module alone: from ansible.module_utils.six import string_types -IMPORT_EXCEPTION = None +YUM_IMPORT_EXCEPTION = None +DNF_IMPORT_EXCEPTION = None +PKG_MGR = None try: import yum # pylint: disable=import-error + PKG_MGR = "yum" except ImportError as err: - IMPORT_EXCEPTION = err + YUM_IMPORT_EXCEPTION = err +try: + import dnf # pylint: disable=import-error + PKG_MGR = "dnf" +except ImportError as err: + DNF_IMPORT_EXCEPTION = err class AosVersionException(Exception): @@ -47,8 +55,11 @@ def main(): supports_check_mode=True ) - if IMPORT_EXCEPTION: - module.fail_json(msg="aos_version module could not import yum: %s" % IMPORT_EXCEPTION) + if YUM_IMPORT_EXCEPTION and DNF_IMPORT_EXCEPTION: + module.fail_json( + msg="aos_version module could not import yum or dnf: %s %s" % + (YUM_IMPORT_EXCEPTION, DNF_IMPORT_EXCEPTION) + ) # determine the packages we will look for package_list = module.params['package_list'] @@ -83,9 +94,6 @@ def _to_dict(pkg_list): def _retrieve_available_packages(expected_pkgs): - # search for package versions available for openshift pkgs - yb = yum.YumBase() # pylint: disable=invalid-name - # The openshift excluder prevents unintended updates to openshift # packages by setting yum excludes on those packages. See: # https://wiki.centos.org/SpecialInterestGroup/PaaS/OpenShift-Origin-Control-Updates @@ -94,17 +102,41 @@ def _retrieve_available_packages(expected_pkgs): # attempt to determine what packages are available via yum they may # be excluded. So, for our purposes here, disable excludes to see # what will really be available during an install or upgrade. - yb.conf.disable_excludes = ['all'] - try: - pkgs = yb.pkgSack.returnPackages(patterns=expected_pkgs) - except yum.Errors.PackageSackError as excinfo: - # you only hit this if *none* of the packages are available - raise AosVersionException('\n'.join([ - 'Unable to find any OpenShift packages.', - 'Check your subscription and repo settings.', - str(excinfo), - ])) + if PKG_MGR == "yum": + # search for package versions available for openshift pkgs + yb = yum.YumBase() # pylint: disable=invalid-name + + yb.conf.disable_excludes = ['all'] + + try: + pkgs = yb.pkgSack.returnPackages(patterns=expected_pkgs) + except yum.Errors.PackageSackError as excinfo: + # you only hit this if *none* of the packages are available + raise AosVersionException('\n'.join([ + 'Unable to find any OpenShift packages.', + 'Check your subscription and repo settings.', + str(excinfo), + ])) + elif PKG_MGR == "dnf": + dbase = dnf.Base() # pyling: disable=invalid-name + + dbase.conf.disable_excludes = ['all'] + dbase.read_all_repos() + dbase.fill_sack(load_system_repo=False, load_available_repos=True) + + dquery = dbase.sack.query() + aquery = dquery.available() + + pkgs = list(aquery.filter(name=expected_pkgs)) + + if not pkgs: + # pkgs list is empty, raise because no expected packages found + raise AosVersionException('\n'.join([ + 'Unable to find any OpenShift packages.', + 'Check your subscription and repo settings.', + ])) + return pkgs diff --git a/roles/openshift_health_checker/library/rpm_version.py b/roles/openshift_health_checker/library/rpm_version.py index 8ea223055..c24fbba3b 100644 --- a/roles/openshift_health_checker/library/rpm_version.py +++ b/roles/openshift_health_checker/library/rpm_version.py @@ -4,6 +4,7 @@ Ansible module for rpm-based systems determining existing package version inform """ from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import string_types IMPORT_EXCEPTION = None try: @@ -82,11 +83,16 @@ def _check_pkg_versions(found_pkgs_dict, expected_pkgs_dict): continue found_versions = [_parse_version(version) for version in found_pkgs_dict[pkg_name]] - expected_version = _parse_version(pkg["version"]) - if expected_version not in found_versions: + + if isinstance(pkg["version"], string_types): + expected_versions = [_parse_version(pkg["version"])] + else: + expected_versions = [_parse_version(version) for version in pkg["version"]] + + if not set(expected_versions) & set(found_versions): invalid_pkg_versions[pkg_name] = { "found_versions": found_versions, - "required_version": expected_version, + "required_versions": expected_versions, } if not_found_pkgs: @@ -106,7 +112,7 @@ def _check_pkg_versions(found_pkgs_dict, expected_pkgs_dict): "The following packages were found to be installed with an incorrect version: {}".format('\n'.join([ " \n{}\n Required version: {}\n Found versions: {}".format( pkg_name, - pkg["required_version"], + ', '.join(pkg["required_versions"]), ', '.join([version for version in pkg["found_versions"]])) for pkg_name, pkg in invalid_pkg_versions.items() ])) diff --git a/roles/openshift_health_checker/meta/main.yml b/roles/openshift_health_checker/meta/main.yml index cd9b55902..bc8e7bdcf 100644 --- a/roles/openshift_health_checker/meta/main.yml +++ b/roles/openshift_health_checker/meta/main.yml @@ -1,4 +1,3 @@ --- dependencies: - - role: openshift_facts - - role: openshift_repos +- role: openshift_facts diff --git a/roles/openshift_health_checker/openshift_checks/etcd_traffic.py b/roles/openshift_health_checker/openshift_checks/etcd_traffic.py index cc1b14d8a..b4c8957e9 100644 --- a/roles/openshift_health_checker/openshift_checks/etcd_traffic.py +++ b/roles/openshift_health_checker/openshift_checks/etcd_traffic.py @@ -14,8 +14,8 @@ class EtcdTraffic(OpenShiftCheck): group_names = self.get_var("group_names", default=[]) valid_group_names = "etcd" in group_names - version = self.get_var("openshift", "common", "short_version") - valid_version = version in ("3.4", "3.5", "1.4", "1.5") + version = self.get_major_minor_version(self.get_var("openshift_image_tag")) + valid_version = version in ((3, 4), (3, 5)) return super(EtcdTraffic, self).is_active() and valid_group_names and valid_version diff --git a/roles/openshift_health_checker/openshift_checks/ovs_version.py b/roles/openshift_health_checker/openshift_checks/ovs_version.py index d5e55bc25..363c12def 100644 --- a/roles/openshift_health_checker/openshift_checks/ovs_version.py +++ b/roles/openshift_health_checker/openshift_checks/ovs_version.py @@ -16,8 +16,8 @@ class OvsVersion(NotContainerizedMixin, OpenShiftCheck): tags = ["health"] openshift_to_ovs_version = { - "3.6": "2.6", - "3.5": "2.6", + "3.6": ["2.6", "2.7"], + "3.5": ["2.6", "2.7"], "3.4": "2.4", } diff --git a/roles/openshift_health_checker/test/etcd_traffic_test.py b/roles/openshift_health_checker/test/etcd_traffic_test.py index f4316c423..fae3e578d 100644 --- a/roles/openshift_health_checker/test/etcd_traffic_test.py +++ b/roles/openshift_health_checker/test/etcd_traffic_test.py @@ -8,7 +8,7 @@ from openshift_checks.etcd_traffic import EtcdTraffic (['masters'], "3.6", False), (['nodes'], "3.4", False), (['etcd'], "3.4", True), - (['etcd'], "3.5", True), + (['etcd'], "1.5", True), (['etcd'], "3.1", False), (['masters', 'nodes'], "3.5", False), (['masters', 'etcd'], "3.5", True), @@ -17,9 +17,7 @@ from openshift_checks.etcd_traffic import EtcdTraffic def test_is_active(group_names, version, is_active): task_vars = dict( group_names=group_names, - openshift=dict( - common=dict(short_version=version), - ), + openshift_image_tag=version, ) assert EtcdTraffic(task_vars=task_vars).is_active() == is_active diff --git a/roles/openshift_health_checker/test/ovs_version_test.py b/roles/openshift_health_checker/test/ovs_version_test.py index b6acef5a6..e1bf29d2a 100644 --- a/roles/openshift_health_checker/test/ovs_version_test.py +++ b/roles/openshift_health_checker/test/ovs_version_test.py @@ -38,8 +38,8 @@ def test_invalid_openshift_release_format(): @pytest.mark.parametrize('openshift_release,expected_ovs_version', [ - ("3.5", "2.6"), - ("3.6", "2.6"), + ("3.5", ["2.6", "2.7"]), + ("3.6", ["2.6", "2.7"]), ("3.4", "2.4"), ("3.3", "2.4"), ("1.0", "2.4"), diff --git a/roles/openshift_health_checker/test/rpm_version_test.py b/roles/openshift_health_checker/test/rpm_version_test.py index 2f09ef965..2c1bcf876 100644 --- a/roles/openshift_health_checker/test/rpm_version_test.py +++ b/roles/openshift_health_checker/test/rpm_version_test.py @@ -49,7 +49,7 @@ def test_check_pkg_found(pkgs, expect_not_found): }, { "eggs": { - "required_version": "3.2", + "required_versions": ["3.2"], "found_versions": ["3.3"], } }, # not the right version @@ -61,11 +61,11 @@ def test_check_pkg_found(pkgs, expect_not_found): }, { "eggs": { - "required_version": "3.2", + "required_versions": ["3.2"], "found_versions": ["3.3", "1.2"], }, "spam": { - "required_version": "3.2", + "required_versions": ["3.2"], "found_versions": ["3.1", "3.3"], } }, # not the right version diff --git a/roles/openshift_hosted/defaults/main.yml b/roles/openshift_hosted/defaults/main.yml index 13cbfb14e..f0e303e43 100644 --- a/roles/openshift_hosted/defaults/main.yml +++ b/roles/openshift_hosted/defaults/main.yml @@ -5,6 +5,9 @@ r_openshift_hosted_router_use_firewalld: False r_openshift_hosted_registry_firewall_enabled: True r_openshift_hosted_registry_use_firewalld: False +openshift_hosted_router_wait: True +openshift_hosted_registry_wait: True + registry_volume_claim: 'registry-claim' openshift_hosted_router_edits: diff --git a/roles/openshift_hosted/tasks/registry/registry.yml b/roles/openshift_hosted/tasks/registry/registry.yml index dcd9c87fc..3e424da12 100644 --- a/roles/openshift_hosted/tasks/registry/registry.yml +++ b/roles/openshift_hosted/tasks/registry/registry.yml @@ -43,9 +43,6 @@ openshift_hosted_registry_images: "{{ openshift.hosted.registry.registryurl | default('openshift3/ose-${component}:${version}')}}" openshift_hosted_registry_volumes: [] openshift_hosted_registry_env_vars: {} - openshift_hosted_registry_routecertificates: "{{ ('routecertificates' in openshift.hosted.registry.keys()) | ternary(openshift.hosted.registry.routecertificates, {}) }}" - openshift_hosted_registry_routehost: "{{ ('routehost' in openshift.hosted.registry.keys()) | ternary(openshift.hosted.registry.routehost, False) }}" - openshift_hosted_registry_routetermination: "{{ ('routetermination' in openshift.hosted.registry.keys()) | ternary(openshift.hosted.registry.routetermination, 'passthrough') }}" openshift_hosted_registry_edits: # These edits are being specified only to prevent 'changed' on rerun - key: spec.strategy.rollingParams @@ -132,34 +129,36 @@ edits: "{{ openshift_hosted_registry_edits }}" force: "{{ True|bool in openshift_hosted_registry_force }}" -- name: Ensure OpenShift registry correctly rolls out (best-effort today) - command: | - oc rollout status deploymentconfig {{ openshift_hosted_registry_name }} \ - --namespace {{ openshift_hosted_registry_namespace }} \ - --config {{ openshift.common.config_base }}/master/admin.kubeconfig - async: 600 - poll: 15 - failed_when: false - -- name: Determine the latest version of the OpenShift registry deployment - command: | - {{ openshift.common.client_binary }} get deploymentconfig {{ openshift_hosted_registry_name }} \ - --namespace {{ openshift_hosted_registry_namespace }} \ - --config {{ openshift.common.config_base }}/master/admin.kubeconfig \ - -o jsonpath='{ .status.latestVersion }' - register: openshift_hosted_registry_latest_version - -- name: Sanity-check that the OpenShift registry rolled out correctly - command: | - {{ openshift.common.client_binary }} get replicationcontroller {{ openshift_hosted_registry_name }}-{{ openshift_hosted_registry_latest_version.stdout }} \ - --namespace {{ openshift_hosted_registry_namespace }} \ - --config {{ openshift.common.config_base }}/master/admin.kubeconfig \ - -o jsonpath='{ .metadata.annotations.openshift\.io/deployment\.phase }' - register: openshift_hosted_registry_rc_phase - until: "'Running' not in openshift_hosted_registry_rc_phase.stdout" - delay: 15 - retries: 40 - failed_when: "'Failed' in openshift_hosted_registry_rc_phase.stdout" +- when: openshift_hosted_registry_wait + block: + - name: Ensure OpenShift registry correctly rolls out (best-effort today) + command: | + oc rollout status deploymentconfig {{ openshift_hosted_registry_name }} \ + --namespace {{ openshift_hosted_registry_namespace }} \ + --config {{ openshift.common.config_base }}/master/admin.kubeconfig + async: 600 + poll: 15 + failed_when: false + + - name: Determine the latest version of the OpenShift registry deployment + command: | + {{ openshift.common.client_binary }} get deploymentconfig {{ openshift_hosted_registry_name }} \ + --namespace {{ openshift_hosted_registry_namespace }} \ + --config {{ openshift.common.config_base }}/master/admin.kubeconfig \ + -o jsonpath='{ .status.latestVersion }' + register: openshift_hosted_registry_latest_version + + - name: Sanity-check that the OpenShift registry rolled out correctly + command: | + {{ openshift.common.client_binary }} get replicationcontroller {{ openshift_hosted_registry_name }}-{{ openshift_hosted_registry_latest_version.stdout }} \ + --namespace {{ openshift_hosted_registry_namespace }} \ + --config {{ openshift.common.config_base }}/master/admin.kubeconfig \ + -o jsonpath='{ .metadata.annotations.openshift\.io/deployment\.phase }' + register: openshift_hosted_registry_rc_phase + until: "'Running' not in openshift_hosted_registry_rc_phase.stdout" + delay: 15 + retries: 40 + failed_when: "'Failed' in openshift_hosted_registry_rc_phase.stdout" - include: storage/glusterfs.yml when: diff --git a/roles/openshift_hosted/tasks/registry/secure.yml b/roles/openshift_hosted/tasks/registry/secure.yml index 29c164f52..a18e6eea9 100644 --- a/roles/openshift_hosted/tasks/registry/secure.yml +++ b/roles/openshift_hosted/tasks/registry/secure.yml @@ -1,65 +1,68 @@ --- -- name: Set fact docker_registry_route_hostname +- name: Configure facts for docker-registry set_fact: - docker_registry_route_hostname: "{{ 'docker-registry-default.' ~ (openshift_master_default_subdomain | default('router.default.svc.cluster.local', true)) }}" + openshift_hosted_registry_routecertificates: "{{ ('routecertificates' in openshift.hosted.registry.keys()) | ternary(openshift.hosted.registry.routecertificates, {}) }}" + openshift_hosted_registry_routehost: "{{ ('routehost' in openshift.hosted.registry.keys()) | ternary(openshift.hosted.registry.routehost, False) }}" + openshift_hosted_registry_routetermination: "{{ ('routetermination' in openshift.hosted.registry.keys()) | ternary(openshift.hosted.registry.routetermination, 'passthrough') }}" -- name: Get the certificate contents for registry - copy: - backup: True - dest: "/etc/origin/master/named_certificates/{{ item.value | basename }}" - src: "{{ item.value }}" - when: item.key in ['certfile', 'keyfile', 'cafile'] and item.value - with_dict: "{{ openshift_hosted_registry_routecertificates }}" +- name: Include reencrypt route configuration + include: secure/reencrypt.yml + static: no + when: openshift_hosted_registry_routetermination == 'reencrypt' -# When certificates are defined we will create the reencrypt -# docker-registry route -- name: Create a reencrypt route for docker-registry - oc_route: - name: docker-registry - namespace: "{{ openshift_hosted_registry_namespace }}" - service_name: docker-registry - tls_termination: "{{ openshift_hosted_registry_routetermination }}" - host: "{{ openshift_hosted_registry_routehost | default(docker_registry_route_hostname) }}" - cert_path: "/etc/origin/master/named_certificates/{{ openshift_hosted_registry_routecertificates['certfile'] | basename }}" - key_path: "/etc/origin/master/named_certificates/{{ openshift_hosted_registry_routecertificates['keyfile'] | basename }}" - cacert_path: "/etc/origin/master/named_certificates/{{ openshift_hosted_registry_routecertificates['cafile'] | basename }}" - dest_cacert_path: /etc/origin/master/ca.crt - when: - - "'cafile' in openshift_hosted_registry_routecertificates" - - "'certfile' in openshift_hosted_registry_routecertificates" - - "'keyfile' in openshift_hosted_registry_routecertificates" +- name: Include passthrough route configuration + include: secure/passthrough.yml + static: no + when: openshift_hosted_registry_routetermination == 'passthrough' -# When routetermination is passthrough we will create the route -- name: Create passthrough route for docker-registry +- name: Fetch the docker-registry route oc_route: name: docker-registry - namespace: "{{ openshift_hosted_registry_namespace }}" - service_name: docker-registry - tls_termination: "{{ openshift_hosted_registry_routetermination }}" - host: "{{ openshift_hosted_registry_routehost | ternary(openshift_hosted_registry_routehost, docker_registry_route_hostname) }}" - when: openshift_hosted_registry_routetermination == 'passthrough' + namespace: default + state: list + register: docker_registry_route -- name: Retrieve registry service IP +- name: Retrieve registry service for the clusterip oc_service: namespace: "{{ openshift_hosted_registry_namespace }}" name: docker-registry state: list - register: docker_registry_service_ip + register: docker_registry_service -- name: Create registry certificates +- name: Generate self-signed docker-registry certificates oc_adm_ca_server_cert: signer_cert: "{{ openshift_master_config_dir }}/ca.crt" signer_key: "{{ openshift_master_config_dir }}/ca.key" signer_serial: "{{ openshift_master_config_dir }}/ca.serial.txt" hostnames: - - "{{ docker_registry_service_ip.results.clusterip }}" - - "{{ openshift_hosted_registry_name }}.default.svc" - - "{{ openshift_hosted_registry_name }}.default.svc.{{ openshift.common.dns_domain }}" - - "{{ docker_registry_route_hostname }}" - cert: "{{ openshift_master_config_dir }}/registry.crt" - key: "{{ openshift_master_config_dir }}/registry.key" + - "{{ docker_registry_service.results.clusterip }}" + - "{{ docker_registry_route.results[0].spec.host }}" + cert: "{{ docker_registry_cert_path }}" + key: "{{ docker_registry_key_path }}" expire_days: "{{ openshift_hosted_registry_cert_expire_days if openshift_version | oo_version_gte_3_5_or_1_5(openshift.common.deployment_type) | bool else omit }}" - register: server_cert_out + register: registry_self_cert + when: docker_registry_self_signed + +# Setting up REGISTRY_HTTP_TLS_CLIENTCAS as the cacert doesn't seem to work. +# If we need to set up a cacert, bundle it with the cert. +- when: docker_registry_cacert_path is defined + block: + - name: Retrieve certificate files to generate certificate bundle + slurp: + src: "{{ item }}" + with_items: + - "{{Â docker_registry_cert_path }}" + - "{{ docker_registry_cacert_path }}" + register: certificate_files + + - name: Generate certificate bundle + copy: + content: "{{ certificate_files.results | map(attribute='content') | map('b64decode') | join('') }}" + dest: "{{ openshift_master_config_dir }}/named_certificates/docker-registry.pem" + + - name: Reset the certificate path to use the bundle + set_fact: + docker_registry_cert_path: "{{ openshift_master_config_dir }}/named_certificates/docker-registry.pem" - name: Create the secret for the registry certificates oc_secret: @@ -67,9 +70,9 @@ namespace: "{{ openshift_hosted_registry_namespace }}" files: - name: registry.crt - path: "{{ openshift_master_config_dir }}/registry.crt" + path: "{{ docker_registry_cert_path }}" - name: registry.key - path: "{{ openshift_master_config_dir }}/registry.key" + path: "{{ docker_registry_key_path }}" register: create_registry_certificates_secret_out - name: Add the secret to the registry's pod service accounts @@ -99,9 +102,15 @@ value: HTTPS action: put +- name: Detect if there has been certificate changes + set_fact: + registry_cert_changed: true + when: ( registry_self_cert is defined and registry_self_cert.changed ) or + create_registry_certificates_secret_out.changed + - name: Update openshift_hosted facts with secure registry variables set_fact: openshift_hosted_registry_volumes: "{{ openshift_hosted_registry_volumes | union(registry_secure_volume_mounts) }}" openshift_hosted_registry_env_vars: "{{ openshift_hosted_registry_env_vars | combine(registry_secure_env_vars) }}" openshift_hosted_registry_edits: "{{ openshift_hosted_registry_edits | union(registry_secure_edits) }}" - openshift_hosted_registry_force: "{{ openshift_hosted_registry_force | union([server_cert_out.changed]) | union([create_registry_certificates_secret_out.changed]) }}" + openshift_hosted_registry_force: "{{ openshift_hosted_registry_force | union([registry_cert_changed | default(false)]) }}" diff --git a/roles/openshift_hosted/tasks/registry/secure/passthrough.yml b/roles/openshift_hosted/tasks/registry/secure/passthrough.yml new file mode 100644 index 000000000..5b44fda10 --- /dev/null +++ b/roles/openshift_hosted/tasks/registry/secure/passthrough.yml @@ -0,0 +1,45 @@ +--- +# Generate a self-signed certificate when there is no user-supplied certificate +- name: Configure self-signed certificate file paths + set_fact: + docker_registry_cert_path: "{{ openshift_master_config_dir }}/registry.crt" + docker_registry_key_path: "{{ openshift_master_config_dir }}/registry.key" + docker_registry_cacert_path: "{{ openshift_master_config_dir }}/ca.crt" + docker_registry_self_signed: true + when: + - "'certfile' not in openshift_hosted_registry_routecertificates" + - "'keyfile' not in openshift_hosted_registry_routecertificates" + +# Retrieve user supplied certificate files if they are provided +- when: + - "'certfile' in openshift_hosted_registry_routecertificates" + - "'keyfile' in openshift_hosted_registry_routecertificates" + block: + - name: Configure provided certificate file paths + set_fact: + docker_registry_cert_path: "{{ openshift_master_config_dir }}/named_certificates/{{ openshift_hosted_registry_routecertificates['certfile'] | basename }}" + docker_registry_key_path: "{{ openshift_master_config_dir }}/named_certificates/{{ openshift_hosted_registry_routecertificates['keyfile'] | basename }}" + docker_registry_self_signed: false + + # Since we end up bundling the cert, cacert and key in a .pem file, the 'cafile' + # is optional + - name: Configure provided ca certificate file path + set_fact: + docker_registry_cacert_path: "{{ openshift_master_config_dir }}/named_certificates/{{ openshift_hosted_registry_routecertificates['cafile'] | basename }}" + when: "'cafile' in openshift_hosted_registry_routecertificates" + + - name: Retrieve provided certificate files + copy: + backup: True + dest: "{{ openshift_master_config_dir }}/named_certificates/{{ item.value | basename }}" + src: "{{ item.value }}" + when: item.key in ['certfile', 'keyfile', 'cafile'] and item.value + with_dict: "{{ openshift_hosted_registry_routecertificates }}" + +- name: Configure a passthrough route for docker-registry + oc_route: + name: docker-registry + namespace: "{{ openshift_hosted_registry_namespace }}" + service_name: docker-registry + tls_termination: "{{ openshift_hosted_registry_routetermination }}" + host: "{{ openshift_hosted_registry_routehost | default(omit, true) }}" diff --git a/roles/openshift_hosted/tasks/registry/secure/reencrypt.yml b/roles/openshift_hosted/tasks/registry/secure/reencrypt.yml new file mode 100644 index 000000000..48e5b0fba --- /dev/null +++ b/roles/openshift_hosted/tasks/registry/secure/reencrypt.yml @@ -0,0 +1,38 @@ +--- +- name: Validate route termination configuration + fail: + msg: > + When 'openshift_hosted_registry_routetermination' is 'reencrypt', you must + provide certificate files with 'openshift_hosted_registry_routecertificates' + when: ('certfile' not in openshift_hosted_registry_routecertificates) or + ('keyfile' not in openshift_hosted_registry_routecertificates) or + ('cafile' not in openshift_hosted_registry_routecertificates) + +- name: Configure self-signed certificate file paths + set_fact: + docker_registry_cert_path: "{{ openshift_master_config_dir }}/registry.crt" + docker_registry_key_path: "{{ openshift_master_config_dir }}/registry.key" + docker_registry_cacert_path: "{{ openshift_master_config_dir }}/ca.crt" + docker_registry_self_signed: true + +- name: Retrieve provided certificate files + copy: + backup: True + dest: "{{ openshift_master_config_dir }}/named_certificates/{{ item.value | basename }}" + src: "{{ item.value }}" + when: item.key in ['certfile', 'keyfile', 'cafile'] and item.value + with_dict: "{{ openshift_hosted_registry_routecertificates }}" + +# Encrypt with the provided certificate and provide the dest_cacert for the +# self-signed certificate at the endpoint +- name: Configure a reencrypt route for docker-registry + oc_route: + name: docker-registry + namespace: "{{ openshift_hosted_registry_namespace }}" + service_name: docker-registry + tls_termination: "{{ openshift_hosted_registry_routetermination }}" + host: "{{ openshift_hosted_registry_routehost | default(omit, true) }}" + cert_path: "{{ openshift_master_config_dir }}/named_certificates/{{ openshift_hosted_registry_routecertificates['certfile'] | basename }}" + key_path: "{{ openshift_master_config_dir }}/named_certificates/{{ openshift_hosted_registry_routecertificates['keyfile'] | basename }}" + cacert_path: "{{ openshift_master_config_dir }}/named_certificates/{{ openshift_hosted_registry_routecertificates['cafile'] | basename }}" + dest_cacert_path: "{{ openshift_master_config_dir }}/ca.crt" diff --git a/roles/openshift_hosted/tasks/registry/storage/object_storage.yml b/roles/openshift_hosted/tasks/registry/storage/object_storage.yml index 8aaba0f3c..8553a8098 100644 --- a/roles/openshift_hosted/tasks/registry/storage/object_storage.yml +++ b/roles/openshift_hosted/tasks/registry/storage/object_storage.yml @@ -2,7 +2,7 @@ - include: s3.yml when: openshift.hosted.registry.storage.provider == 's3' -- name: Ensure the resgistry secret exists +- name: Ensure the registry secret exists oc_secret: name: "{{ registry_config_secret_name }}" state: present @@ -10,6 +10,19 @@ - path: /tmp/config.yml data: "{{ lookup('template', 'registry_config.j2') }}" register: registry_config_out + when: openshift_hosted_registry_storage_gcs_keyfile is not defined + +- name: Ensure the registry secret exists for GCS + oc_secret: + name: "{{ registry_config_secret_name }}" + state: present + contents: + - path: /tmp/config.yml + data: "{{ lookup('template', 'registry_config.j2') }}" + - path: /tmp/gcs.json + data: "{{ lookup('file', openshift_hosted_registry_storage_gcs_keyfile) | string }}" + register: registry_config_out + when: openshift_hosted_registry_storage_gcs_keyfile is defined - name: Add secrets to registry service account oc_serviceaccount_secret: diff --git a/roles/openshift_hosted/tasks/registry/storage/registry_config_secret.j2 b/roles/openshift_hosted/tasks/registry/storage/registry_config_secret.j2 deleted file mode 120000 index b9e82c1ea..000000000 --- a/roles/openshift_hosted/tasks/registry/storage/registry_config_secret.j2 +++ /dev/null @@ -1 +0,0 @@ -../../../templates/registry_config_secret.j2
\ No newline at end of file diff --git a/roles/openshift_hosted/tasks/router/router.yml b/roles/openshift_hosted/tasks/router/router.yml index 72a1ead80..e57ed733e 100644 --- a/roles/openshift_hosted/tasks/router/router.yml +++ b/roles/openshift_hosted/tasks/router/router.yml @@ -94,36 +94,38 @@ stats_port: "{{ item.stats_port }}" with_items: "{{ openshift_hosted_routers }}" -- name: Ensure OpenShift router correctly rolls out (best-effort today) - command: | - {{ openshift.common.client_binary }} rollout status deploymentconfig {{ item.name }} \ - --namespace {{ item.namespace | default('default') }} \ - --config {{ openshift.common.config_base }}/master/admin.kubeconfig - async: 600 - poll: 15 - with_items: "{{ openshift_hosted_routers }}" - failed_when: false +- when: openshift_hosted_router_wait + block: + - name: Ensure OpenShift router correctly rolls out (best-effort today) + command: | + {{ openshift.common.client_binary }} rollout status deploymentconfig {{ item.name }} \ + --namespace {{ item.namespace | default('default') }} \ + --config {{ openshift.common.config_base }}/master/admin.kubeconfig + async: 600 + poll: 15 + with_items: "{{ openshift_hosted_routers }}" + failed_when: false -- name: Determine the latest version of the OpenShift router deployment - command: | - {{ openshift.common.client_binary }} get deploymentconfig {{ item.name }} \ - --namespace {{ item.namespace }} \ - --config {{ openshift.common.config_base }}/master/admin.kubeconfig \ - -o jsonpath='{ .status.latestVersion }' - register: openshift_hosted_routers_latest_version - with_items: "{{ openshift_hosted_routers }}" + - name: Determine the latest version of the OpenShift router deployment + command: | + {{ openshift.common.client_binary }} get deploymentconfig {{ item.name }} \ + --namespace {{ item.namespace }} \ + --config {{ openshift.common.config_base }}/master/admin.kubeconfig \ + -o jsonpath='{ .status.latestVersion }' + register: openshift_hosted_routers_latest_version + with_items: "{{ openshift_hosted_routers }}" -- name: Poll for OpenShift router deployment success - command: | - {{ openshift.common.client_binary }} get replicationcontroller {{ item.0.name }}-{{ item.1.stdout }} \ - --namespace {{ item.0.namespace }} \ - --config {{ openshift.common.config_base }}/master/admin.kubeconfig \ - -o jsonpath='{ .metadata.annotations.openshift\.io/deployment\.phase }' - register: openshift_hosted_router_rc_phase - until: "'Running' not in openshift_hosted_router_rc_phase.stdout" - delay: 15 - retries: 40 - failed_when: "'Failed' in openshift_hosted_router_rc_phase.stdout" - with_together: - - "{{ openshift_hosted_routers }}" - - "{{ openshift_hosted_routers_latest_version.results }}" + - name: Poll for OpenShift router deployment success + command: | + {{ openshift.common.client_binary }} get replicationcontroller {{ item.0.name }}-{{ item.1.stdout }} \ + --namespace {{ item.0.namespace }} \ + --config {{ openshift.common.config_base }}/master/admin.kubeconfig \ + -o jsonpath='{ .metadata.annotations.openshift\.io/deployment\.phase }' + register: openshift_hosted_router_rc_phase + until: "'Running' not in openshift_hosted_router_rc_phase.stdout" + delay: 15 + retries: 40 + failed_when: "'Failed' in openshift_hosted_router_rc_phase.stdout" + with_together: + - "{{ openshift_hosted_routers }}" + - "{{ openshift_hosted_routers_latest_version.results }}" diff --git a/roles/openshift_hosted/templates/registry_config.j2 b/roles/openshift_hosted/templates/registry_config.j2 index fc9272679..61da452de 100644 --- a/roles/openshift_hosted/templates/registry_config.j2 +++ b/roles/openshift_hosted/templates/registry_config.j2 @@ -60,7 +60,7 @@ storage: gcs: bucket: {{ openshift_hosted_registry_storage_gcs_bucket }} {% if openshift_hosted_registry_storage_gcs_keyfile is defined %} - keyfile: {{ openshift_hosted_registry_storage_gcs_keyfile }} + keyfile: /etc/registry/gcs.json {% endif -%} {% if openshift_hosted_registry_storage_gcs_rootdirectory is defined %} rootdirectory: {{ openshift_hosted_registry_storage_gcs_rootdirectory }} diff --git a/roles/openshift_hosted/templates/registry_config_secret.j2 b/roles/openshift_hosted/templates/registry_config_secret.j2 deleted file mode 100644 index ca68544ec..000000000 --- a/roles/openshift_hosted/templates/registry_config_secret.j2 +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: v1 -kind: Secret -metadata: - name: registry-config - annotations: - provider: {{ openshift.hosted.registry.storage.provider }} -data: - config.yml: {{ registry_config }} diff --git a/roles/openshift_loadbalancer/meta/main.yml b/roles/openshift_loadbalancer/meta/main.yml index 073bdd94d..72298b599 100644 --- a/roles/openshift_loadbalancer/meta/main.yml +++ b/roles/openshift_loadbalancer/meta/main.yml @@ -12,4 +12,3 @@ galaxy_info: dependencies: - role: lib_os_firewall - role: openshift_facts -- role: openshift_repos diff --git a/roles/openshift_logging_elasticsearch/defaults/main.yml b/roles/openshift_logging_elasticsearch/defaults/main.yml index c0b5d394e..0690bf114 100644 --- a/roles/openshift_logging_elasticsearch/defaults/main.yml +++ b/roles/openshift_logging_elasticsearch/defaults/main.yml @@ -37,6 +37,9 @@ openshift_logging_elasticsearch_storage_group: '65534' openshift_logging_es_pvc_prefix: "{{ openshift_hosted_logging_elasticsearch_pvc_prefix | default('logging-es') }}" +# config the es plugin to write kibana index based on the index mode +openshift_logging_elasticsearch_kibana_index_mode: 'unique' + # this is used to determine if this is an operations deployment or a non-ops deployment # simply used for naming purposes openshift_logging_elasticsearch_ops_deployment: false diff --git a/roles/openshift_logging_elasticsearch/tasks/main.yaml b/roles/openshift_logging_elasticsearch/tasks/main.yaml index aae23668a..28c3ffd96 100644 --- a/roles/openshift_logging_elasticsearch/tasks/main.yaml +++ b/roles/openshift_logging_elasticsearch/tasks/main.yaml @@ -102,9 +102,21 @@ delete_after: true # configmap +- assert: + that: + - openshift_logging_elasticsearch_kibana_index_mode in __kibana_index_modes + msg: "The openshift_logging_elasticsearch_kibana_index_mode '{{ openshift_logging_elasticsearch_kibana_index_mode }}' only supports one of: {{ __kibana_index_modes | join(', ') }}" + +- assert: + that: + - "{{ openshift_logging_es_log_appenders | length > 0 }}" + msg: "The openshift_logging_es_log_appenders '{{ openshift_logging_es_log_appenders }}' has an unrecognized option and only supports the following as a list: {{ __es_log_appenders | join(', ') }}" + - template: src: elasticsearch-logging.yml.j2 dest: "{{ tempdir }}/elasticsearch-logging.yml" + vars: + root_logger: "{{openshift_logging_es_log_appenders | join(', ')}}" when: es_logging_contents is undefined changed_when: no @@ -115,6 +127,8 @@ allow_cluster_reader: "{{ openshift_logging_elasticsearch_ops_allow_cluster_reader | lower | default('false') }}" es_number_of_shards: "{{ openshift_logging_es_number_of_shards | default(1) }}" es_number_of_replicas: "{{ openshift_logging_es_number_of_replicas | default(0) }}" + es_kibana_index_mode: "{{ openshift_logging_elasticsearch_kibana_index_mode | default('unique') }}" + when: es_config_contents is undefined changed_when: no diff --git a/roles/openshift_logging_elasticsearch/templates/elasticsearch-logging.yml.j2 b/roles/openshift_logging_elasticsearch/templates/elasticsearch-logging.yml.j2 index 38948ba2f..c7b2b2721 100644 --- a/roles/openshift_logging_elasticsearch/templates/elasticsearch-logging.yml.j2 +++ b/roles/openshift_logging_elasticsearch/templates/elasticsearch-logging.yml.j2 @@ -1,14 +1,26 @@ # you can override this using by setting a system property, for example -Des.logger.level=DEBUG es.logger.level: INFO -rootLogger: ${es.logger.level}, console, file +rootLogger: ${es.logger.level}, {{root_logger}} logger: # log action execution errors for easier debugging action: WARN + + # + # deprecation logging, turn to DEBUG to see them + deprecation: WARN, deprecation_log_file + # reduce the logging for aws, too much is logged under the default INFO com.amazonaws: WARN + io.fabric8.elasticsearch: ${PLUGIN_LOGLEVEL} io.fabric8.kubernetes: ${PLUGIN_LOGLEVEL} + # aws will try to do some sketchy JMX stuff, but its not needed. + com.amazonaws.jmx.SdkMBeanRegistrySupport: ERROR + com.amazonaws.metrics.AwsSdkMetrics: ERROR + + org.apache.http: INFO + # gateway #gateway: DEBUG #index.gateway: DEBUG @@ -28,13 +40,14 @@ logger: additivity: index.search.slowlog: false index.indexing.slowlog: false + deprecation: false appender: console: type: console layout: type: consolePattern - conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %.1000m%n" # need this filter until https://github.com/openshift/origin/issues/14515 is fixed filter: 1: @@ -67,6 +80,14 @@ appender: #type: pattern #conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + deprecation_log_file: + type: dailyRollingFile + file: ${path.logs}/${cluster.name}_deprecation.log + datePattern: "'.'yyyy-MM-dd" + layout: + type: pattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + index_search_slow_log_file: type: dailyRollingFile file: ${path.logs}/${cluster.name}_index_search_slowlog.log diff --git a/roles/openshift_logging_elasticsearch/templates/elasticsearch.yml.j2 b/roles/openshift_logging_elasticsearch/templates/elasticsearch.yml.j2 index 141967c33..0c06a7677 100644 --- a/roles/openshift_logging_elasticsearch/templates/elasticsearch.yml.j2 +++ b/roles/openshift_logging_elasticsearch/templates/elasticsearch.yml.j2 @@ -53,6 +53,8 @@ openshift.searchguard: openshift.operations.allow_cluster_reader: {{allow_cluster_reader | default (false)}} +openshift.kibana.index.mode: {{es_kibana_index_mode | default('unique')}} + path: data: /elasticsearch/persistent/${CLUSTER_NAME}/data logs: /elasticsearch/${CLUSTER_NAME}/logs diff --git a/roles/openshift_logging_elasticsearch/vars/main.yml b/roles/openshift_logging_elasticsearch/vars/main.yml index 7a1f5048b..20fa63543 100644 --- a/roles/openshift_logging_elasticsearch/vars/main.yml +++ b/roles/openshift_logging_elasticsearch/vars/main.yml @@ -2,6 +2,8 @@ __latest_es_version: "3_5" __allowed_es_versions: ["3_5", "3_6"] __allowed_es_types: ["data-master", "data-client", "master", "client"] +__es_log_appenders: ['file', 'console'] +__kibana_index_modes: ["unique", "shared_ops"] # TODO: integrate these openshift_master_config_dir: "{{ openshift.common.config_base }}/master" diff --git a/roles/openshift_logging_kibana/defaults/main.yml b/roles/openshift_logging_kibana/defaults/main.yml index b2556fd71..14787a62b 100644 --- a/roles/openshift_logging_kibana/defaults/main.yml +++ b/roles/openshift_logging_kibana/defaults/main.yml @@ -26,7 +26,7 @@ openshift_logging_kibana_ops_deployment: false # Proxy settings openshift_logging_kibana_proxy_debug: false openshift_logging_kibana_proxy_cpu_limit: null -openshift_logging_kibana_proxy_memory_limit: 96Mi +openshift_logging_kibana_proxy_memory_limit: 256Mi #The absolute path on the control node to the cert file to use #for the public facing kibana certs diff --git a/roles/openshift_master/handlers/main.yml b/roles/openshift_master/handlers/main.yml index ce7688581..d5094c2c9 100644 --- a/roles/openshift_master/handlers/main.yml +++ b/roles/openshift_master/handlers/main.yml @@ -1,12 +1,21 @@ --- - name: restart master api - systemd: name={{ openshift.common.service_type }}-master-api state=restarted - when: (not (master_api_service_status_changed | default(false) | bool)) and openshift.master.cluster_method == 'native' - notify: Verify API Server + systemd: + name: "{{ openshift.common.service_type }}-master-api" + state: restarted + when: + - not (master_api_service_status_changed | default(false) | bool) + - openshift.master.cluster_method == 'native' + notify: + - Verify API Server - name: restart master controllers - systemd: name={{ openshift.common.service_type }}-master-controllers state=restarted - when: (not (master_controllers_service_status_changed | default(false) | bool)) and openshift.master.cluster_method == 'native' + systemd: + name: "{{ openshift.common.service_type }}-master-controllers" + state: restarted + when: + - not (master_controllers_service_status_changed | default(false) | bool) + - openshift.master.cluster_method == 'native' - name: Verify API Server # Using curl here since the uri module requires python-httplib2 and @@ -23,8 +32,8 @@ # Disables the following warning: # Consider using get_url or uri module rather than running curl warn: no - register: api_available_output - until: api_available_output.stdout == 'ok' + register: l_api_available_output + until: l_api_available_output.stdout == 'ok' retries: 120 delay: 1 changed_when: false diff --git a/roles/openshift_master/tasks/bootstrap.yml b/roles/openshift_master/tasks/bootstrap.yml new file mode 100644 index 000000000..0013f5289 --- /dev/null +++ b/roles/openshift_master/tasks/bootstrap.yml @@ -0,0 +1,28 @@ +--- + +- name: ensure the node-bootstrap service account exists + oc_serviceaccount: + name: node-bootstrapper + namespace: openshift-infra + state: present + run_once: true + +- name: grant node-bootstrapper the correct permissions to bootstrap + oc_adm_policy_user: + namespace: openshift-infra + user: system:serviceaccount:openshift-infra:node-bootstrapper + resource_kind: cluster-role + resource_name: system:node-bootstrapper + state: present + run_once: true + +# TODO: create a module for this command. +# oc_serviceaccounts_kubeconfig +- name: create service account kubeconfig with csr rights + command: "oc serviceaccounts create-kubeconfig node-bootstrapper -n openshift-infra" + register: kubeconfig_out + +- name: put service account kubeconfig into a file on disk for bootstrap + copy: + content: "{{ kubeconfig_out.stdout }}" + dest: "{{ openshift_master_config_dir }}/bootstrap.kubeconfig" diff --git a/roles/openshift_master/tasks/clean_systemd_units.yml b/roles/openshift_master/tasks/clean_systemd_units.yml index 590692c10..e641f84d4 100644 --- a/roles/openshift_master/tasks/clean_systemd_units.yml +++ b/roles/openshift_master/tasks/clean_systemd_units.yml @@ -1,5 +1,9 @@ --- - name: Disable master service - systemd: name={{ openshift.common.service_type }}-master state=stopped enabled=no masked=yes + systemd: + name: "{{ openshift.common.service_type }}-master" + state: stopped + enabled: no + masked: yes ignore_errors: true diff --git a/roles/openshift_master/tasks/firewall.yml b/roles/openshift_master/tasks/firewall.yml index e51eeb56e..38afb6764 100644 --- a/roles/openshift_master/tasks/firewall.yml +++ b/roles/openshift_master/tasks/firewall.yml @@ -7,7 +7,8 @@ action: add protocol: "{{ item.port.split('/')[1] }}" port: "{{ item.port.split('/')[0] }}" - when: item.cond | default(True) + when: + - item.cond | default(True) with_items: "{{ r_openshift_master_os_firewall_allow }}" - name: Remove iptables rules @@ -16,7 +17,8 @@ action: remove protocol: "{{ item.port.split('/')[1] }}" port: "{{ item.port.split('/')[0] }}" - when: item.cond | default(True) + when: + - item.cond | default(True) with_items: "{{ r_openshift_master_os_firewall_deny }}" - when: r_openshift_master_firewall_enabled | bool and r_openshift_master_use_firewalld | bool @@ -27,7 +29,8 @@ permanent: true immediate: true state: enabled - when: item.cond | default(True) + when: + - item.cond | default(True) with_items: "{{ r_openshift_master_os_firewall_allow }}" - name: Remove firewalld allow rules @@ -36,5 +39,6 @@ permanent: true immediate: true state: disabled - when: item.cond | default(True) + when: + - item.cond | default(True) with_items: "{{ r_openshift_master_os_firewall_deny }}" diff --git a/roles/openshift_master/tasks/main.yml b/roles/openshift_master/tasks/main.yml index b1412c3d9..b80941b48 100644 --- a/roles/openshift_master/tasks/main.yml +++ b/roles/openshift_master/tasks/main.yml @@ -7,21 +7,34 @@ - fail: msg: > Invalid OAuth grant method: {{ openshift_master_oauth_grant_method }} - when: openshift_master_oauth_grant_method is defined and openshift_master_oauth_grant_method not in openshift_master_valid_grant_methods + when: + - openshift_master_oauth_grant_method is defined + - openshift_master_oauth_grant_method not in openshift_master_valid_grant_methods # HA Variable Validation - fail: msg: "openshift_master_cluster_method must be set to either 'native' or 'pacemaker' for multi-master installations" - when: openshift.master.ha | bool and ((openshift.master.cluster_method is not defined) or (openshift.master.cluster_method is defined and openshift.master.cluster_method not in ["native", "pacemaker"])) + when: + - openshift.master.ha | bool + - (openshift.master.cluster_method is not defined) or (openshift.master.cluster_method is defined and openshift.master.cluster_method not in ["native", "pacemaker"]) - fail: msg: "'native' high availability is not supported for the requested OpenShift version" - when: openshift.master.ha | bool and openshift.master.cluster_method == "native" and not openshift.common.version_gte_3_1_or_1_1 | bool + when: + - openshift.master.ha | bool + - openshift.master.cluster_method == "native" + - not openshift.common.version_gte_3_1_or_1_1 | bool - fail: msg: "openshift_master_cluster_password must be set for multi-master installations" - when: openshift.master.ha | bool and openshift.master.cluster_method == "pacemaker" and (openshift_master_cluster_password is not defined or not openshift_master_cluster_password) + when: + - openshift.master.ha | bool + - openshift.master.cluster_method == "pacemaker" + - openshift_master_cluster_password is not defined or not openshift_master_cluster_password - fail: msg: "Pacemaker based HA is not supported at this time when used with containerized installs" - when: openshift.master.ha | bool and openshift.master.cluster_method == "pacemaker" and openshift.common.is_containerized | bool + when: + - openshift.master.ha | bool + - openshift.master.cluster_method == "pacemaker" + - openshift.common.is_containerized | bool - name: Open up firewall ports include: firewall.yml @@ -31,7 +44,8 @@ package: name: "{{ openshift.common.service_type }}-master{{ openshift_pkg_version | default('') | oo_image_tag_to_rpm_version(include_dash=True) }}" state: present - when: not openshift.common.is_containerized | bool + when: + - not openshift.common.is_containerized | bool - name: Create openshift.common.data_dir file: @@ -40,11 +54,13 @@ mode: 0755 owner: root group: root - when: openshift.common.is_containerized | bool + when: + - openshift.common.is_containerized | bool - name: Reload systemd units command: systemctl daemon-reload - when: openshift.common.is_containerized | bool and install_result | changed + when: + - openshift.common.is_containerized | bool - name: Re-gather package dependent master facts openshift_facts: @@ -61,8 +77,8 @@ args: creates: "{{ openshift_master_policy }}" notify: - - restart master api - - restart master controllers + - restart master api + - restart master controllers - name: Create the scheduler config copy: @@ -70,20 +86,22 @@ dest: "{{ openshift_master_scheduler_conf }}" backup: true notify: - - restart master api - - restart master controllers + - restart master api + - restart master controllers - name: Install httpd-tools if needed package: name=httpd-tools state=present - when: (item.kind == 'HTPasswdPasswordIdentityProvider') and - not openshift.common.is_atomic | bool + when: + - item.kind == 'HTPasswdPasswordIdentityProvider' + - not openshift.common.is_atomic | bool with_items: "{{ openshift.master.identity_providers }}" - name: Ensure htpasswd directory exists file: path: "{{ item.filename | dirname }}" state: directory - when: item.kind == 'HTPasswdPasswordIdentityProvider' + when: + - item.kind == 'HTPasswdPasswordIdentityProvider' with_items: "{{ openshift.master.identity_providers }}" - name: Create the htpasswd file if needed @@ -91,7 +109,9 @@ dest: "{{ item.filename }}" src: htpasswd.j2 backup: yes - when: item.kind == 'HTPasswdPasswordIdentityProvider' and openshift.master.manage_htpasswd | bool + when: + - item.kind == 'HTPasswdPasswordIdentityProvider' + - openshift.master.manage_htpasswd | bool with_items: "{{ openshift.master.identity_providers }}" - name: Ensure htpasswd file exists @@ -100,7 +120,8 @@ force: no content: "" mode: 0600 - when: item.kind == 'HTPasswdPasswordIdentityProvider' + when: + - item.kind == 'HTPasswdPasswordIdentityProvider' with_items: "{{ openshift.master.identity_providers }}" - name: Create the ldap ca file if needed @@ -109,7 +130,9 @@ content: "{{ openshift.master.ldap_ca }}" mode: 0600 backup: yes - when: openshift.master.ldap_ca is defined and item.kind == 'LDAPPasswordIdentityProvider' + when: + - openshift.master.ldap_ca is defined + - item.kind == 'LDAPPasswordIdentityProvider' with_items: "{{ openshift.master.identity_providers }}" - name: Create the openid ca file if needed @@ -118,7 +141,10 @@ content: "{{ openshift.master.openid_ca }}" mode: 0600 backup: yes - when: openshift.master.openid_ca is defined and item.kind == 'OpenIDIdentityProvider' and item.ca | default('') != '' + when: + - openshift.master.openid_ca is defined + - item.kind == 'OpenIDIdentityProvider' + - item.ca | default('') != '' with_items: "{{ openshift.master.identity_providers }}" - name: Create the request header ca file if needed @@ -127,20 +153,23 @@ content: "{{ openshift.master.request_header_ca }}" mode: 0600 backup: yes - when: openshift.master.request_header_ca is defined and item.kind == 'RequestHeaderIdentityProvider' and item.clientCA | default('') != '' + when: + - openshift.master.request_header_ca is defined + - item.kind == 'RequestHeaderIdentityProvider' + - item.clientCA | default('') != '' with_items: "{{ openshift.master.identity_providers }}" # This is an ugly hack to verify settings are in a file without modifying them with lineinfile. # The template file will stomp any other settings made. - block: - - name: check whether our docker-registry setting exists in the env file - command: "awk '/^OPENSHIFT_DEFAULT_REGISTRY=docker-registry.default.svc:5000/' /etc/sysconfig/{{ openshift.common.service_type }}-master" - failed_when: false - changed_when: false - register: already_set + - name: check whether our docker-registry setting exists in the env file + command: "awk '/^OPENSHIFT_DEFAULT_REGISTRY=docker-registry.default.svc:5000/' /etc/sysconfig/{{ openshift.common.service_type }}-master" + failed_when: false + changed_when: false + register: l_already_set - - set_fact: - openshift_push_via_dns: "{{ (openshift_use_dnsmasq | default(true) and openshift.common.version_gte_3_6) or (already_set.stdout is defined and already_set.stdout | match('OPENSHIFT_DEFAULT_REGISTRY=docker-registry.default.svc:5000')) }}" + - set_fact: + openshift_push_via_dns: "{{ (openshift_use_dnsmasq | default(true) and openshift.common.version_gte_3_6) or (l_already_set.stdout is defined and l_already_set.stdout | match('OPENSHIFT_DEFAULT_REGISTRY=docker-registry.default.svc:5000')) }}" - name: Set fact of all etcd host IPs openshift_facts: @@ -156,7 +185,9 @@ - name: Install Master system container include: system_container.yml - when: openshift.common.is_containerized | bool and openshift.common.is_master_system_container | bool + when: + - openshift.common.is_containerized | bool + - openshift.common.is_master_system_container | bool - name: Create session secrets file template: @@ -165,9 +196,11 @@ owner: root group: root mode: 0600 - when: openshift.master.session_auth_secrets is defined and openshift.master.session_encryption_secrets is defined + when: + - openshift.master.session_auth_secrets is defined + - openshift.master.session_encryption_secrets is defined notify: - - restart master api + - restart master api - set_fact: translated_identity_providers: "{{ openshift.master.identity_providers | translate_idps('v1', openshift.common.version, openshift.common.deployment_type) }}" @@ -182,53 +215,80 @@ group: root mode: 0600 notify: - - restart master api - - restart master controllers + - restart master api + - restart master controllers + +- name: modify controller args + yedit: + src: /etc/origin/master/master-config.yaml + edits: + - key: kubernetesMasterConfig.controllerArguments.cluster-signing-cert-file + value: + - /etc/origin/master/ca.crt + - key: kubernetesMasterConfig.controllerArguments.cluster-signing-key-file + value: + - /etc/origin/master/ca.key + notify: + - restart master controllers + when: openshift_master_bootstrap_enabled | default(False) - include: set_loopback_context.yml - when: openshift.common.version_gte_3_2_or_1_2 + when: + - openshift.common.version_gte_3_2_or_1_2 - name: Start and enable master api on first master systemd: name: "{{ openshift.common.service_type }}-master-api" enabled: yes state: started - when: openshift.master.cluster_method == 'native' and inventory_hostname == openshift_master_hosts[0] - register: start_result - until: not start_result | failed + when: + - openshift.master.cluster_method == 'native' + - inventory_hostname == openshift_master_hosts[0] + register: l_start_result + until: not l_start_result | failed retries: 1 delay: 60 - name: Dump logs from master-api if it failed command: journalctl --no-pager -n 100 -u {{ openshift.common.service_type }}-master-api - when: start_result | failed + when: + - l_start_result | failed - set_fact: - master_api_service_status_changed: "{{ start_result | changed }}" - when: openshift.master.cluster_method == 'native' and inventory_hostname == openshift_master_hosts[0] + master_api_service_status_changed: "{{ l_start_result | changed }}" + when: + - openshift.master.cluster_method == 'native' + - inventory_hostname == openshift_master_hosts[0] - pause: seconds: 15 - when: openshift.master.ha | bool and openshift.master.cluster_method == 'native' + when: + - openshift.master.ha | bool + - openshift.master.cluster_method == 'native' - name: Start and enable master api all masters systemd: name: "{{ openshift.common.service_type }}-master-api" enabled: yes state: started - when: openshift.master.cluster_method == 'native' and inventory_hostname != openshift_master_hosts[0] - register: start_result - until: not start_result | failed + when: + - openshift.master.cluster_method == 'native' + - inventory_hostname != openshift_master_hosts[0] + register: l_start_result + until: not l_start_result | failed retries: 1 delay: 60 - name: Dump logs from master-api if it failed command: journalctl --no-pager -n 100 -u {{ openshift.common.service_type }}-master-api - when: start_result | failed + when: + - l_start_result | failed - set_fact: - master_api_service_status_changed: "{{ start_result | changed }}" - when: openshift.master.cluster_method == 'native' and inventory_hostname != openshift_master_hosts[0] + master_api_service_status_changed: "{{ l_start_result | changed }}" + when: + - openshift.master.cluster_method == 'native' + - inventory_hostname != openshift_master_hosts[0] # A separate wait is required here for native HA since notifies will # be resolved after all tasks in the role. @@ -243,67 +303,84 @@ --cacert {{ openshift.common.config_base }}/master/ca.crt {% endif %} {{ openshift.master.api_url }}/healthz/ready - register: api_available_output - until: api_available_output.stdout == 'ok' + register: l_api_available_output + until: l_api_available_output.stdout == 'ok' retries: 120 delay: 1 run_once: true changed_when: false - when: openshift.master.cluster_method == 'native' and master_api_service_status_changed | bool + when: + - openshift.master.cluster_method == 'native' + - master_api_service_status_changed | bool - name: Start and enable master controller on first master systemd: name: "{{ openshift.common.service_type }}-master-controllers" enabled: yes state: started - when: openshift.master.cluster_method == 'native' and inventory_hostname == openshift_master_hosts[0] - register: start_result - until: not start_result | failed + when: + - openshift.master.cluster_method == 'native' + - inventory_hostname == openshift_master_hosts[0] + register: l_start_result + until: not l_start_result | failed retries: 1 delay: 60 - name: Dump logs from master-controllers if it failed command: journalctl --no-pager -n 100 -u {{ openshift.common.service_type }}-master-controllers - when: start_result | failed + when: + - l_start_result | failed - name: Wait for master controller service to start on first master pause: seconds: 15 - when: openshift.master.cluster_method == 'native' + when: + - openshift.master.cluster_method == 'native' - name: Start and enable master controller on all masters systemd: name: "{{ openshift.common.service_type }}-master-controllers" enabled: yes state: started - when: openshift.master.cluster_method == 'native' and inventory_hostname != openshift_master_hosts[0] - register: start_result - until: not start_result | failed + when: + - openshift.master.cluster_method == 'native' + - inventory_hostname != openshift_master_hosts[0] + register: l_start_result + until: not l_start_result | failed retries: 1 delay: 60 - name: Dump logs from master-controllers if it failed command: journalctl --no-pager -n 100 -u {{ openshift.common.service_type }}-master-controllers - when: start_result | failed + when: + - l_start_result | failed - set_fact: - master_controllers_service_status_changed: "{{ start_result | changed }}" - when: openshift.master.cluster_method == 'native' + master_controllers_service_status_changed: "{{ l_start_result | changed }}" + when: + - openshift.master.cluster_method == 'native' - name: Install cluster packages package: name=pcs state=present - when: openshift.master.cluster_method == 'pacemaker' - and not openshift.common.is_containerized | bool - register: install_result + when: + - openshift.master.cluster_method == 'pacemaker' + - not openshift.common.is_containerized | bool + register: l_install_result - name: Start and enable cluster service systemd: name: pcsd enabled: yes state: started - when: openshift.master.cluster_method == 'pacemaker' - and not openshift.common.is_containerized | bool + when: + - openshift.master.cluster_method == 'pacemaker' + - not openshift.common.is_containerized | bool - name: Set the cluster user password shell: echo {{ openshift_master_cluster_password | quote }} | passwd --stdin hacluster - when: install_result | changed + when: + - l_install_result | changed + +- name: node bootstrap settings + include: bootstrap.yml + when: openshift_master_bootstrap_enabled | default(False) diff --git a/roles/openshift_master/tasks/set_loopback_context.yml b/roles/openshift_master/tasks/set_loopback_context.yml index 9c3fb31dc..308b2f4cd 100644 --- a/roles/openshift_master/tasks/set_loopback_context.yml +++ b/roles/openshift_master/tasks/set_loopback_context.yml @@ -4,7 +4,7 @@ {{ openshift.common.client_binary }} config view --config={{ openshift_master_loopback_config }} changed_when: false - register: loopback_config + register: l_loopback_config - command: > {{ openshift.common.client_binary }} config set-cluster @@ -12,7 +12,8 @@ --embed-certs=true --server={{ openshift.master.loopback_api_url }} {{ openshift.master.loopback_cluster_name }} --config={{ openshift_master_loopback_config }} - when: loopback_context_string not in loopback_config.stdout + when: + - loopback_context_string not in l_loopback_config.stdout register: set_loopback_cluster - command: > @@ -21,11 +22,13 @@ --namespace=default --user={{ openshift.master.loopback_user }} {{ openshift.master.loopback_context_name }} --config={{ openshift_master_loopback_config }} - when: set_loopback_cluster | changed - register: set_loopback_context + when: + - set_loopback_cluster | changed + register: l_set_loopback_context - command: > {{ openshift.common.client_binary }} config use-context {{ openshift.master.loopback_context_name }} --config={{ openshift_master_loopback_config }} - when: set_loopback_context | changed + when: + - l_set_loopback_context | changed register: set_current_context diff --git a/roles/openshift_master/tasks/system_container.yml b/roles/openshift_master/tasks/system_container.yml index 8d343336f..91332acfb 100644 --- a/roles/openshift_master/tasks/system_container.yml +++ b/roles/openshift_master/tasks/system_container.yml @@ -2,13 +2,12 @@ - name: Pre-pull master system container image command: > atomic pull --storage=ostree {{ 'docker:' if openshift.common.system_images_registry == 'docker' else openshift.common.system_images_registry + '/' }}{{ openshift.master.master_system_image }}:{{ openshift_image_tag }} - register: pull_result - changed_when: "'Pulling layer' in pull_result.stdout" + register: l_pull_result + changed_when: "'Pulling layer' in l_pull_result.stdout" - name: Check Master system container package command: > atomic containers list --no-trunc -a -f container={{ openshift.common.service_type }}-master - register: result # HA - name: Install or Update HA api master system container @@ -17,7 +16,7 @@ image: "{{ 'docker:' if openshift.common.system_images_registry == 'docker' else openshift.common.system_images_registry + '/' }}{{ openshift.master.master_system_image }}:{{ openshift_image_tag }}" state: latest values: - - COMMAND=api + - COMMAND=api - name: Install or Update HA controller master system container oc_atomic_container: @@ -25,4 +24,4 @@ image: "{{ 'docker:' if openshift.common.system_images_registry == 'docker' else openshift.common.system_images_registry + '/' }}{{ openshift.master.master_system_image }}:{{ openshift_image_tag }}" state: latest values: - - COMMAND=controllers + - COMMAND=controllers diff --git a/roles/openshift_master/tasks/systemd_units.yml b/roles/openshift_master/tasks/systemd_units.yml index 723bdb0c4..72c231e52 100644 --- a/roles/openshift_master/tasks/systemd_units.yml +++ b/roles/openshift_master/tasks/systemd_units.yml @@ -12,15 +12,18 @@ set_fact: containerized_svc_dir: "/etc/systemd/system" ha_svc_template_path: "docker-cluster" - when: openshift.common.is_containerized | bool + when: + - openshift.common.is_containerized | bool # This is the image used for both HA and non-HA clusters: - name: Pre-pull master image command: > docker pull {{ openshift.master.master_image }}:{{ openshift_image_tag }} - register: pull_result - changed_when: "'Downloaded newer image' in pull_result.stdout" - when: openshift.common.is_containerized | bool and not openshift.common.is_master_system_container | bool + register: l_pull_result + changed_when: "'Downloaded newer image' in l_pull_result.stdout" + when: + - openshift.common.is_containerized | bool + - not openshift.common.is_master_system_container | bool - name: Create the ha systemd unit files template: @@ -32,23 +35,26 @@ with_items: - api - controllers - register: create_ha_unit_files + register: l_create_ha_unit_files - command: systemctl daemon-reload - when: create_ha_unit_files | changed + when: + - l_create_ha_unit_files | changed # end workaround for missing systemd unit files - name: Preserve Master API Proxy Config options command: grep PROXY /etc/sysconfig/{{ openshift.common.service_type }}-master-api - register: master_api_proxy - when: openshift.master.cluster_method == "native" + register: l_master_api_proxy + when: + - openshift.master.cluster_method == "native" failed_when: false changed_when: false - name: Preserve Master API AWS options command: grep AWS_ /etc/sysconfig/{{ openshift.common.service_type }}-master-api register: master_api_aws - when: openshift.master.cluster_method == "native" + when: + - openshift.master.cluster_method == "native" failed_when: false changed_when: false @@ -57,22 +63,27 @@ src: "{{ ha_svc_template_path }}/atomic-openshift-master-api.j2" dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-api backup: true - when: openshift.master.cluster_method == "native" + when: + - openshift.master.cluster_method == "native" notify: - restart master api - name: Restore Master API Proxy Config Options - when: openshift.master.cluster_method == "native" - and master_api_proxy.rc == 0 and 'http_proxy' not in openshift.common and 'https_proxy' not in openshift.common + when: + - openshift.master.cluster_method == "native" + - l_master_api_proxy.rc == 0 + - "'http_proxy' not in openshift.common" + - "'https_proxy' not in openshift.common" lineinfile: dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-api line: "{{ item }}" - with_items: "{{ master_api_proxy.stdout_lines | default([]) }}" + with_items: "{{ l_master_api_proxy.stdout_lines | default([]) }}" - name: Restore Master API AWS Options - when: openshift.master.cluster_method == "native" - and master_api_aws.rc == 0 and - not (openshift_cloudprovider_kind is defined and openshift_cloudprovider_kind == 'aws' and openshift_cloudprovider_aws_access_key is defined and openshift_cloudprovider_aws_secret_key is defined) + when: + - openshift.master.cluster_method == "native" + - master_api_aws.rc == 0 + - not (openshift_cloudprovider_kind is defined and openshift_cloudprovider_kind == 'aws' and openshift_cloudprovider_aws_access_key is defined and openshift_cloudprovider_aws_secret_key is defined) lineinfile: dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-api line: "{{ item }}" @@ -82,14 +93,16 @@ - name: Preserve Master Controllers Proxy Config options command: grep PROXY /etc/sysconfig/{{ openshift.common.service_type }}-master-controllers register: master_controllers_proxy - when: openshift.master.cluster_method == "native" + when: + - openshift.master.cluster_method == "native" failed_when: false changed_when: false - name: Preserve Master Controllers AWS options command: grep AWS_ /etc/sysconfig/{{ openshift.common.service_type }}-master-controllers register: master_controllers_aws - when: openshift.master.cluster_method == "native" + when: + - openshift.master.cluster_method == "native" failed_when: false changed_when: false @@ -98,7 +111,8 @@ src: "{{ ha_svc_template_path }}/atomic-openshift-master-controllers.j2" dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-controllers backup: true - when: openshift.master.cluster_method == "native" + when: + - openshift.master.cluster_method == "native" notify: - restart master controllers @@ -107,14 +121,18 @@ dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-controllers line: "{{ item }}" with_items: "{{ master_controllers_proxy.stdout_lines | default([]) }}" - when: openshift.master.cluster_method == "native" - and master_controllers_proxy.rc == 0 and 'http_proxy' not in openshift.common and 'https_proxy' not in openshift.common + when: + - openshift.master.cluster_method == "native" + - master_controllers_proxy.rc == 0 + - "'http_proxy' not in openshift.common" + - "'https_proxy' not in openshift.common" - name: Restore Master Controllers AWS Options lineinfile: dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-controllers line: "{{ item }}" with_items: "{{ master_controllers_aws.stdout_lines | default([]) }}" - when: openshift.master.cluster_method == "native" - and master_controllers_aws.rc == 0 and - not (openshift_cloudprovider_kind is defined and openshift_cloudprovider_kind == 'aws' and openshift_cloudprovider_aws_access_key is defined and openshift_cloudprovider_aws_secret_key is defined) + when: + - openshift.master.cluster_method == "native" + - master_controllers_aws.rc == 0 + - not (openshift_cloudprovider_kind is defined and openshift_cloudprovider_kind == 'aws' and openshift_cloudprovider_aws_access_key is defined and openshift_cloudprovider_aws_secret_key is defined) diff --git a/roles/openshift_master_cluster/meta/main.yml b/roles/openshift_master_cluster/meta/main.yml index f2a67bc54..c452b165e 100644 --- a/roles/openshift_master_cluster/meta/main.yml +++ b/roles/openshift_master_cluster/meta/main.yml @@ -12,5 +12,4 @@ galaxy_info: categories: - cloud - system -dependencies: -- { role: openshift_repos } +dependencies: [] diff --git a/roles/openshift_node_dnsmasq/meta/main.yml b/roles/openshift_node_dnsmasq/meta/main.yml index 18e04e06d..84035b88c 100644 --- a/roles/openshift_node_dnsmasq/meta/main.yml +++ b/roles/openshift_node_dnsmasq/meta/main.yml @@ -13,3 +13,4 @@ galaxy_info: - cloud dependencies: - role: openshift_common +- role: openshift_node_facts diff --git a/roles/openshift_node_dnsmasq/templates/origin-dns.conf.j2 b/roles/openshift_node_dnsmasq/templates/origin-dns.conf.j2 index 779b4d2f5..ef3ba2880 100644 --- a/roles/openshift_node_dnsmasq/templates/origin-dns.conf.j2 +++ b/roles/openshift_node_dnsmasq/templates/origin-dns.conf.j2 @@ -4,4 +4,4 @@ no-negcache max-cache-ttl=1 enable-dbus bind-interfaces -listen-address={{ ansible_default_ipv4.address }} +listen-address={{ openshift.node.dns_ip }} diff --git a/roles/openshift_repos/meta/main.yml b/roles/openshift_repos/meta/main.yml deleted file mode 100644 index 1b043863b..000000000 --- a/roles/openshift_repos/meta/main.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -galaxy_info: - author: TODO - description: OpenShift Repositories - company: Red Hat, Inc. - license: Apache License, Version 2.0 - min_ansible_version: 1.7 - platforms: - - name: EL - versions: - - 7 - categories: - - cloud -dependencies: -- role: openshift_sanitize_inventory diff --git a/roles/openshift_storage_glusterfs/defaults/main.yml b/roles/openshift_storage_glusterfs/defaults/main.yml index ff2c18812..a5887465e 100644 --- a/roles/openshift_storage_glusterfs/defaults/main.yml +++ b/roles/openshift_storage_glusterfs/defaults/main.yml @@ -52,3 +52,15 @@ openshift_storage_glusterfs_registry_heketi_ssh_port: "{{ openshift_storage_glus openshift_storage_glusterfs_registry_heketi_ssh_user: "{{ openshift_storage_glusterfs_heketi_ssh_user }}" openshift_storage_glusterfs_registry_heketi_ssh_sudo: "{{ openshift_storage_glusterfs_heketi_ssh_sudo }}" openshift_storage_glusterfs_registry_heketi_ssh_keyfile: "{{ openshift_storage_glusterfs_heketi_ssh_keyfile | default(omit) }}" +r_openshift_master_firewall_enabled: True +r_openshift_master_use_firewalld: False +r_openshift_storage_glusterfs_os_firewall_deny: [] +r_openshift_storage_glusterfs_os_firewall_allow: +- service: glusterfs_sshd + port: "2222/tcp" +- service: glusterfs_daemon + port: "24007/tcp" +- service: glusterfs_management + port: "24008/tcp" +- service: glusterfs_bricks + port: "49152-49251/tcp" diff --git a/roles/openshift_storage_glusterfs/meta/main.yml b/roles/openshift_storage_glusterfs/meta/main.yml index aab9851f9..0cdd33880 100644 --- a/roles/openshift_storage_glusterfs/meta/main.yml +++ b/roles/openshift_storage_glusterfs/meta/main.yml @@ -11,5 +11,5 @@ galaxy_info: - 7 dependencies: - role: openshift_hosted_facts -- role: openshift_repos - role: lib_openshift +- role: lib_os_firewall diff --git a/roles/openshift_storage_glusterfs/tasks/firewall.yml b/roles/openshift_storage_glusterfs/tasks/firewall.yml new file mode 100644 index 000000000..09dcf1ef9 --- /dev/null +++ b/roles/openshift_storage_glusterfs/tasks/firewall.yml @@ -0,0 +1,40 @@ +--- +- when: r_openshift_storage_glusterfs_firewall_enabled | bool and not r_openshift_storage_glusterfs_use_firewalld | bool + block: + - name: Add iptables allow rules + os_firewall_manage_iptables: + name: "{{ item.service }}" + action: add + protocol: "{{ item.port.split('/')[1] }}" + port: "{{ item.port.split('/')[0] }}" + when: item.cond | default(True) + with_items: "{{ r_openshift_storage_glusterfs_os_firewall_allow }}" + + - name: Remove iptables rules + os_firewall_manage_iptables: + name: "{{ item.service }}" + action: remove + protocol: "{{ item.port.split('/')[1] }}" + port: "{{ item.port.split('/')[0] }}" + when: item.cond | default(True) + with_items: "{{ r_openshift_storage_glusterfs_os_firewall_deny }}" + +- when: r_openshift_storage_glusterfs_firewall_enabled | bool and r_openshift_storage_glusterfs_use_firewalld | bool + block: + - name: Add firewalld allow rules + firewalld: + port: "{{ item.port }}" + permanent: true + immediate: true + state: enabled + when: item.cond | default(True) + with_items: "{{ r_openshift_storage_glusterfs_os_firewall_allow }}" + + - name: Remove firewalld allow rules + firewalld: + port: "{{ item.port }}" + permanent: true + immediate: true + state: disabled + when: item.cond | default(True) + with_items: "{{ r_openshift_storage_glusterfs_os_firewall_deny }}" diff --git a/roles/openshift_storage_nfs/meta/main.yml b/roles/openshift_storage_nfs/meta/main.yml index b360d0658..98f7c317e 100644 --- a/roles/openshift_storage_nfs/meta/main.yml +++ b/roles/openshift_storage_nfs/meta/main.yml @@ -12,4 +12,3 @@ galaxy_info: dependencies: - role: lib_os_firewall - role: openshift_hosted_facts -- role: openshift_repos diff --git a/roles/openshift_version/meta/main.yml b/roles/openshift_version/meta/main.yml index ca896addd..38b398343 100644 --- a/roles/openshift_version/meta/main.yml +++ b/roles/openshift_version/meta/main.yml @@ -12,7 +12,6 @@ galaxy_info: categories: - cloud dependencies: -- role: openshift_repos - role: openshift_docker_facts - role: docker when: openshift.common.is_containerized | default(False) | bool and not skip_docker_role | default(False) | bool diff --git a/roles/os_firewall/meta/main.yml b/roles/os_firewall/meta/main.yml deleted file mode 100644 index dca5fc5ff..000000000 --- a/roles/os_firewall/meta/main.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -galaxy_info: - author: Jason DeTiberus - description: os_firewall - company: Red Hat, Inc. - license: Apache License, Version 2.0 - min_ansible_version: 2.2 - platforms: - - name: EL - versions: - - 7 - categories: - - system -allow_duplicates: yes -dependencies: - - role: openshift_facts diff --git a/roles/os_firewall/tasks/firewall/firewalld.yml b/roles/os_firewall/tasks/firewalld.yml index 2cc7af478..54430f402 100644 --- a/roles/os_firewall/tasks/firewall/firewalld.yml +++ b/roles/os_firewall/tasks/firewalld.yml @@ -1,4 +1,9 @@ --- +- name: Fail - Firewalld is not supported on Atomic Host + fail: + msg: "Firewalld is not supported on Atomic Host" + when: r_os_firewall_is_atomic | bool + - name: Install firewalld packages package: name: firewalld @@ -31,7 +36,8 @@ register: result - name: need to pause here, otherwise the firewalld service starting can sometimes cause ssh to fail - pause: seconds=10 + pause: + seconds: 10 when: result | changed - name: Restart polkitd diff --git a/roles/os_firewall/tasks/firewall/iptables.yml b/roles/os_firewall/tasks/iptables.yml index ccb3c4713..0af5abf38 100644 --- a/roles/os_firewall/tasks/firewall/iptables.yml +++ b/roles/os_firewall/tasks/iptables.yml @@ -15,11 +15,13 @@ when: task_result | changed - name: Install iptables packages - package: name={{ item }} state=present + package: + name: "{{ item }}" + state: present with_items: - iptables - iptables-services - when: not openshift.common.is_atomic | bool + when: not r_os_firewall_is_atomic | bool - name: Start and enable iptables service systemd: @@ -29,7 +31,11 @@ masked: no daemon_reload: yes register: result + delegate_to: "{{item}}" + run_once: true + with_items: "{{ ansible_play_hosts }}" - name: need to pause here, otherwise the iptables service starting can sometimes cause ssh to fail - pause: seconds=10 + pause: + seconds: 10 when: result | changed diff --git a/roles/os_firewall/tasks/main.yml b/roles/os_firewall/tasks/main.yml index 20efe5b0d..c477d386c 100644 --- a/roles/os_firewall/tasks/main.yml +++ b/roles/os_firewall/tasks/main.yml @@ -1,12 +1,19 @@ --- -- name: Assert - Do not use firewalld on Atomic Host - assert: - that: not os_firewall_use_firewalld | bool - msg: "Firewalld is not supported on Atomic Host" - when: openshift.common.is_atomic | bool +- name: Detecting Atomic Host Operating System + stat: + path: /run/ostree-booted + register: r_os_firewall_ostree_booted -- include: firewall/firewalld.yml - when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool +- name: Set fact r_os_firewall_is_atomic + set_fact: + r_os_firewall_is_atomic: "{{ r_os_firewall_ostree_booted.stat.exists }}" -- include: firewall/iptables.yml - when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool +- include: firewalld.yml + when: + - os_firewall_enabled | bool + - os_firewall_use_firewalld | bool + +- include: iptables.yml + when: + - os_firewall_enabled | bool + - not os_firewall_use_firewalld | bool diff --git a/roles/rhel_subscribe/tasks/enterprise.yml b/roles/rhel_subscribe/tasks/enterprise.yml index ea0c42150..39d59db70 100644 --- a/roles/rhel_subscribe/tasks/enterprise.yml +++ b/roles/rhel_subscribe/tasks/enterprise.yml @@ -7,7 +7,7 @@ when: deployment_type == 'enterprise' - set_fact: - default_ose_version: '3.5' + default_ose_version: '3.6' when: deployment_type in ['atomic-enterprise', 'openshift-enterprise'] - set_fact: @@ -16,7 +16,7 @@ - fail: msg: "{{ ose_version }} is not a valid version for {{ deployment_type }} deployment type" when: ( deployment_type == 'enterprise' and ose_version not in ['3.0'] ) or - ( deployment_type in ['atomic-enterprise', 'openshift-enterprise'] and ose_version not in ['3.1', '3.2', '3.3', '3.4', '3.5'] ) + ( deployment_type in ['atomic-enterprise', 'openshift-enterprise'] and ose_version not in ['3.1', '3.2', '3.3', '3.4', '3.5', '3.6'] ) - name: Enable RHEL repositories command: subscription-manager repos \ |