diff options
99 files changed, 6680 insertions, 577 deletions
| diff --git a/callback_plugins/aa_version_requirement.py b/callback_plugins/aa_version_requirement.py index 40affb18b..f31445381 100644 --- a/callback_plugins/aa_version_requirement.py +++ b/callback_plugins/aa_version_requirement.py @@ -7,6 +7,7 @@ The plugin is named with leading `aa_` to ensure this plugin is loaded  first (alphanumerically) by Ansible.  """  import sys +from subprocess import check_output  from ansible import __version__  if __version__ < '2.0': @@ -65,7 +66,11 @@ class CallbackModule(CallbackBase):              sys.exit(1)          if __version__ == '2.2.1.0': -            display( -                'FATAL: Current Ansible version (%s) is not supported. %s' -                % (__version__, FAIL_ON_2_2_1_0), color='red') -            sys.exit(1) +            rpm_ver = str(check_output(["rpm", "-qa", "ansible"])) +            patched_ansible = '2.2.1.0-2' + +            if patched_ansible not in rpm_ver: +                display( +                    'FATAL: Current Ansible version (%s) is not supported. %s' +                    % (__version__, FAIL_ON_2_2_1_0), color='red') +                sys.exit(1) diff --git a/filter_plugins/openshift_master.py b/filter_plugins/openshift_master.py index f209d6c3b..4ccee91f9 100644 --- a/filter_plugins/openshift_master.py +++ b/filter_plugins/openshift_master.py @@ -517,23 +517,17 @@ class FilterModule(object):          return valid      @staticmethod -    def certificates_to_synchronize(hostvars, include_keys=True): +    def certificates_to_synchronize(hostvars, include_keys=True, include_ca=True):          ''' Return certificates to synchronize based on facts. '''          if not issubclass(type(hostvars), dict):              raise errors.AnsibleFilterError("|failed expects hostvars is a dict") -        certs = ['ca.crt', -                 'ca.key', -                 'admin.crt', +        certs = ['admin.crt',                   'admin.key',                   'admin.kubeconfig',                   'master.kubelet-client.crt', -                 'master.kubelet-client.key', -                 'openshift-registry.crt', -                 'openshift-registry.key', -                 'openshift-registry.kubeconfig', -                 'openshift-router.crt', -                 'openshift-router.key', -                 'openshift-router.kubeconfig'] +                 'master.kubelet-client.key'] +        if bool(include_ca): +            certs += ['ca.crt', 'ca.key']          if bool(include_keys):              certs += ['serviceaccounts.private.key',                        'serviceaccounts.public.key'] @@ -547,6 +541,13 @@ class FilterModule(object):          if bool(hostvars['openshift']['common']['version_gte_3_3_or_1_3']):              certs += ['service-signer.crt',                        'service-signer.key'] +        if not bool(hostvars['openshift']['common']['version_gte_3_5_or_1_5']): +            certs += ['openshift-registry.crt', +                      'openshift-registry.key', +                      'openshift-registry.kubeconfig', +                      'openshift-router.crt', +                      'openshift-router.key', +                      'openshift-router.kubeconfig']          return certs      @staticmethod diff --git a/inventory/byo/hosts.origin.example b/inventory/byo/hosts.origin.example index 0a1b8c5c4..f24cfc737 100644 --- a/inventory/byo/hosts.origin.example +++ b/inventory/byo/hosts.origin.example @@ -79,7 +79,7 @@ openshift_release=v1.4  # Disable pushing to dockerhub  #openshift_docker_disable_push_dockerhub=True  # Items added, as is, to end of /etc/sysconfig/docker OPTIONS -# Default value: "--log-driver=json-file --log-opt max-size=50m" +# Default value: "--log-driver=journald"  #openshift_docker_options="-l warn --ipv6=false"  # Specify exact version of Docker to configure or upgrade to. @@ -528,10 +528,7 @@ openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true',  # NOTE: CA certificate will not be replaced with existing clusters.  # This option may only be specified when creating a new cluster or  # when redeploying cluster certificates with the redeploy-certificates -# playbook. If replacing the CA certificate in an existing cluster -# with a custom ca certificate, the following variable must also be -# set. -#openshift_certificates_redeploy_ca=true +# playbook.  # Configure custom named certificates (SNI certificates)  # diff --git a/inventory/byo/hosts.ose.example b/inventory/byo/hosts.ose.example index 89b9d7e48..b48776304 100644 --- a/inventory/byo/hosts.ose.example +++ b/inventory/byo/hosts.ose.example @@ -79,7 +79,7 @@ openshift_release=v3.4  # Disable pushing to dockerhub  #openshift_docker_disable_push_dockerhub=True  # Items added, as is, to end of /etc/sysconfig/docker OPTIONS -# Default value: "--log-driver=json-file --log-opt max-size=50m" +# Default value: "--log-driver=journald"  #openshift_docker_options="-l warn --ipv6=false"  # Specify exact version of Docker to configure or upgrade to. @@ -528,10 +528,7 @@ openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true',  # NOTE: CA certificate will not be replaced with existing clusters.  # This option may only be specified when creating a new cluster or  # when redeploying cluster certificates with the redeploy-certificates -# playbook. If replacing the CA certificate in an existing cluster -# with a custom ca certificate, the following variable must also be -# set. -#openshift_certificates_redeploy_ca=true +# playbook.  # Configure custom named certificates (SNI certificates)  # diff --git a/library/kubeclient_ca.py b/library/kubeclient_ca.py new file mode 100644 index 000000000..163624a76 --- /dev/null +++ b/library/kubeclient_ca.py @@ -0,0 +1,90 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vim: expandtab:tabstop=4:shiftwidth=4 + +''' kubeclient_ca ansible module ''' + +import base64 +import yaml +from ansible.module_utils.basic import AnsibleModule + + +DOCUMENTATION = ''' +--- +module: kubeclient_ca +short_description: Modify kubeclient certificate-authority-data +author: Andrew Butcher +requirements: [ ] +''' +EXAMPLES = ''' +- kubeclient_ca: +    client_path: /etc/origin/master/admin.kubeconfig +    ca_path: /etc/origin/master/ca-bundle.crt + +- slurp: +    src: /etc/origin/master/ca-bundle.crt +  register: ca_data +- kubeclient_ca: +    client_path: /etc/origin/master/admin.kubeconfig +    ca_data: "{{ ca_data.content }}" +''' + + +def main(): +    ''' Modify kubeconfig located at `client_path`, setting the +        certificate authority data to specified `ca_data` or contents of +        `ca_path`. +    ''' + +    module = AnsibleModule(  # noqa: F405 +        argument_spec=dict( +            client_path=dict(required=True), +            ca_data=dict(required=False, default=None), +            ca_path=dict(required=False, default=None), +            backup=dict(required=False, default=True, type='bool'), +        ), +        supports_check_mode=True, +        mutually_exclusive=[['ca_data', 'ca_path']], +        required_one_of=[['ca_data', 'ca_path']] +    ) + +    client_path = module.params['client_path'] +    ca_data = module.params['ca_data'] +    ca_path = module.params['ca_path'] +    backup = module.params['backup'] + +    try: +        with open(client_path) as client_config_file: +            client_config_data = yaml.safe_load(client_config_file.read()) + +        if ca_data is None: +            with open(ca_path) as ca_file: +                ca_data = base64.standard_b64encode(ca_file.read()) + +        changes = [] +        # Naively update the CA information for each cluster in the +        # kubeconfig. +        for cluster in client_config_data['clusters']: +            if cluster['cluster']['certificate-authority-data'] != ca_data: +                cluster['cluster']['certificate-authority-data'] = ca_data +                changes.append(cluster['name']) + +        if not module.check_mode: +            if len(changes) > 0 and backup: +                module.backup_local(client_path) + +            with open(client_path, 'w') as client_config_file: +                client_config_string = yaml.dump(client_config_data, default_flow_style=False) +                client_config_string = client_config_string.replace('\'\'', '""') +                client_config_file.write(client_config_string) + +        return module.exit_json(changed=(len(changes) > 0)) + +    # ignore broad-except error to avoid stack trace to ansible user +    # pylint: disable=broad-except +    except Exception as error: +        return module.fail_json(msg=str(error)) + + +if __name__ == '__main__': +    main() diff --git a/openshift-ansible.spec b/openshift-ansible.spec index 9faf3e78e..8cec36bb7 100644 --- a/openshift-ansible.spec +++ b/openshift-ansible.spec @@ -20,6 +20,7 @@ Requires:      tar  Requires:      openshift-ansible-docs = %{version}-%{release}  Requires:      java-1.8.0-openjdk-headless  Requires:      httpd-tools +Requires:      python-ruamel-yaml  %description  Openshift and Atomic Enterprise Ansible diff --git a/playbooks/byo/openshift-cluster/config.yml b/playbooks/byo/openshift-cluster/config.yml index d953b8ed3..86eff4ca4 100644 --- a/playbooks/byo/openshift-cluster/config.yml +++ b/playbooks/byo/openshift-cluster/config.yml @@ -1,27 +1,7 @@  --- -- name: Create initial host groups for localhost -  hosts: localhost -  connection: local -  become: no -  gather_facts: no +- include: ../../common/openshift-cluster/std_include.yml    tags:    - always -  tasks: -  - include_vars: ../../byo/openshift-cluster/cluster_hosts.yml -  - name: Evaluate group l_oo_all_hosts -    add_host: -      name: "{{ item }}" -      groups: l_oo_all_hosts -    with_items: "{{ g_all_hosts | default([]) }}" -    changed_when: no - -- name: Create initial host groups for all hosts -  hosts: l_oo_all_hosts -  gather_facts: no -  tags: -  - always -  tasks: -  - include_vars: ../../byo/openshift-cluster/cluster_hosts.yml  - include: ../../common/openshift-cluster/config.yml    vars: diff --git a/playbooks/byo/openshift-cluster/redeploy-certificates.yml b/playbooks/byo/openshift-cluster/redeploy-certificates.yml index 753248855..ad24b9ad0 100644 --- a/playbooks/byo/openshift-cluster/redeploy-certificates.yml +++ b/playbooks/byo/openshift-cluster/redeploy-certificates.yml @@ -1,28 +1,20 @@  --- -- name: Create initial host groups for localhost -  hosts: localhost -  connection: local -  become: no -  gather_facts: no +- include: ../../common/openshift-cluster/std_include.yml    tags:    - always -  tasks: -  - include_vars: ../../byo/openshift-cluster/cluster_hosts.yml -  - name: Evaluate group l_oo_all_hosts -    add_host: -      name: "{{ item }}" -      groups: l_oo_all_hosts -    with_items: "{{ g_all_hosts | default([]) }}" -    changed_when: False - -- name: Create initial host groups for all hosts -  hosts: l_oo_all_hosts -  gather_facts: no -  tags: -  - always -  tasks: -  - include_vars: ../../byo/openshift-cluster/cluster_hosts.yml -- include: ../../common/openshift-cluster/redeploy-certificates.yml -  vars: -    openshift_deployment_type: "{{ deployment_type }}" +- include: ../../common/openshift-cluster/redeploy-certificates/etcd.yml + +- include: ../../common/openshift-cluster/redeploy-certificates/masters.yml + +- include: ../../common/openshift-cluster/redeploy-certificates/nodes.yml + +- include: ../../common/openshift-etcd/restart.yml + +- include: ../../common/openshift-master/restart.yml + +- include: ../../common/openshift-node/restart.yml + +- include: ../../common/openshift-cluster/redeploy-certificates/router.yml + +- include: ../../common/openshift-cluster/redeploy-certificates/registry.yml diff --git a/playbooks/byo/openshift-cluster/redeploy-etcd-certificates.yml b/playbooks/byo/openshift-cluster/redeploy-etcd-certificates.yml new file mode 100644 index 000000000..ee49364fa --- /dev/null +++ b/playbooks/byo/openshift-cluster/redeploy-etcd-certificates.yml @@ -0,0 +1,10 @@ +--- +- include: ../../common/openshift-cluster/std_include.yml +  tags: +  - always + +- include: ../../common/openshift-cluster/redeploy-certificates/etcd.yml + +- include: ../../common/openshift-etcd/restart.yml + +- include: ../../common/openshift-master/restart.yml diff --git a/playbooks/byo/openshift-cluster/redeploy-master-certificates.yml b/playbooks/byo/openshift-cluster/redeploy-master-certificates.yml new file mode 100644 index 000000000..9c8248c4e --- /dev/null +++ b/playbooks/byo/openshift-cluster/redeploy-master-certificates.yml @@ -0,0 +1,8 @@ +--- +- include: ../../common/openshift-cluster/std_include.yml +  tags: +  - always + +- include: ../../common/openshift-cluster/redeploy-certificates/masters.yml + +- include: ../../common/openshift-master/restart.yml diff --git a/playbooks/byo/openshift-cluster/redeploy-node-certificates.yml b/playbooks/byo/openshift-cluster/redeploy-node-certificates.yml new file mode 100644 index 000000000..1695111d0 --- /dev/null +++ b/playbooks/byo/openshift-cluster/redeploy-node-certificates.yml @@ -0,0 +1,8 @@ +--- +- include: ../../common/openshift-cluster/std_include.yml +  tags: +  - always + +- include: ../../common/openshift-cluster/redeploy-certificates/nodes.yml + +- include: ../../common/openshift-node/restart.yml diff --git a/playbooks/byo/openshift-cluster/redeploy-openshift-ca.yml b/playbooks/byo/openshift-cluster/redeploy-openshift-ca.yml new file mode 100644 index 000000000..e44e95467 --- /dev/null +++ b/playbooks/byo/openshift-cluster/redeploy-openshift-ca.yml @@ -0,0 +1,6 @@ +--- +- include: ../../common/openshift-cluster/std_include.yml +  tags: +  - always + +- include: ../../common/openshift-cluster/redeploy-certificates/ca.yml diff --git a/playbooks/byo/openshift-cluster/redeploy-registry-certificates.yml b/playbooks/byo/openshift-cluster/redeploy-registry-certificates.yml new file mode 100644 index 000000000..53ee68db9 --- /dev/null +++ b/playbooks/byo/openshift-cluster/redeploy-registry-certificates.yml @@ -0,0 +1,6 @@ +--- +- include: ../../common/openshift-cluster/std_include.yml +  tags: +  - always + +- include: ../../common/openshift-cluster/redeploy-certificates/registry.yml diff --git a/playbooks/byo/openshift-cluster/redeploy-router-certificates.yml b/playbooks/byo/openshift-cluster/redeploy-router-certificates.yml new file mode 100644 index 000000000..f8c267569 --- /dev/null +++ b/playbooks/byo/openshift-cluster/redeploy-router-certificates.yml @@ -0,0 +1,6 @@ +--- +- include: ../../common/openshift-cluster/std_include.yml +  tags: +  - always + +- include: ../../common/openshift-cluster/redeploy-certificates/router.yml diff --git a/playbooks/byo/openshift-etcd/filter_plugins b/playbooks/byo/openshift-etcd/filter_plugins new file mode 120000 index 000000000..99a95e4ca --- /dev/null +++ b/playbooks/byo/openshift-etcd/filter_plugins @@ -0,0 +1 @@ +../../../filter_plugins
\ No newline at end of file diff --git a/playbooks/byo/openshift-etcd/lookup_plugins b/playbooks/byo/openshift-etcd/lookup_plugins new file mode 120000 index 000000000..ac79701db --- /dev/null +++ b/playbooks/byo/openshift-etcd/lookup_plugins @@ -0,0 +1 @@ +../../../lookup_plugins
\ No newline at end of file diff --git a/playbooks/byo/openshift-etcd/restart.yml b/playbooks/byo/openshift-etcd/restart.yml new file mode 100644 index 000000000..6713f07e3 --- /dev/null +++ b/playbooks/byo/openshift-etcd/restart.yml @@ -0,0 +1,8 @@ +--- +- include: ../../common/openshift-cluster/std_include.yml +  tags: +  - always + +- include: ../../common/openshift-etcd/restart.yml +  vars: +    openshift_deployment_type: "{{ deployment_type }}" diff --git a/playbooks/byo/openshift-etcd/roles b/playbooks/byo/openshift-etcd/roles new file mode 120000 index 000000000..20c4c58cf --- /dev/null +++ b/playbooks/byo/openshift-etcd/roles @@ -0,0 +1 @@ +../../../roles
\ No newline at end of file diff --git a/playbooks/byo/openshift-master/restart.yml b/playbooks/byo/openshift-master/restart.yml index 3e58ccbcc..2d20f69f4 100644 --- a/playbooks/byo/openshift-master/restart.yml +++ b/playbooks/byo/openshift-master/restart.yml @@ -1,38 +1,8 @@  --- -- name: Create initial host groups for localhost -  hosts: localhost -  connection: local -  become: no -  gather_facts: no +- include: ../../common/openshift-cluster/std_include.yml    tags:    - always -  tasks: -  - include_vars: ../../byo/openshift-cluster/cluster_hosts.yml -  - name: Evaluate group l_oo_all_hosts -    add_host: -      name: "{{ item }}" -      groups: l_oo_all_hosts -    with_items: "{{ g_all_hosts | default([]) }}" -    changed_when: False -- name: Create initial host groups for all hosts -  hosts: l_oo_all_hosts -  gather_facts: no -  tags: -  - always -  tasks: -  - include_vars: ../../byo/openshift-cluster/cluster_hosts.yml - -- include: ../../common/openshift-cluster/evaluate_groups.yml -- include: ../../common/openshift-master/validate_restart.yml - -- name: Restart masters -  hosts: oo_masters_to_config +- include: ../../common/openshift-master/restart.yml    vars: -    openshift_master_ha: "{{ groups.oo_masters_to_config | length > 1 }}" -  serial: 1 -  tasks: -  - include: restart_hosts.yml -    when: openshift.common.rolling_restart_mode == 'system' -  - include: restart_services.yml -    when: openshift.common.rolling_restart_mode == 'services' +    openshift_deployment_type: "{{ deployment_type }}" diff --git a/playbooks/byo/openshift-node/restart.yml b/playbooks/byo/openshift-node/restart.yml new file mode 100644 index 000000000..3985a83bb --- /dev/null +++ b/playbooks/byo/openshift-node/restart.yml @@ -0,0 +1,8 @@ +--- +- include: ../../common/openshift-cluster/std_include.yml +  tags: +  - always + +- include: ../../common/openshift-node/restart.yml +  vars: +    openshift_deployment_type: "{{ deployment_type }}" diff --git a/playbooks/byo/openshift_facts.yml b/playbooks/byo/openshift_facts.yml index 025983662..fcf402fa0 100644 --- a/playbooks/byo/openshift_facts.yml +++ b/playbooks/byo/openshift_facts.yml @@ -1,29 +1,7 @@  --- -- name: Create initial host groups for localhost -  hosts: localhost -  connection: local -  become: no -  gather_facts: no +- include: ../../common/openshift-cluster/std_include.yml    tags:    - always -  tasks: -  - include_vars: ../byo/openshift-cluster/cluster_hosts.yml -  - name: Evaluate group l_oo_all_hosts -    add_host: -      name: "{{ item }}" -      groups: l_oo_all_hosts -    with_items: "{{ g_all_hosts | default([]) }}" -    changed_when: False - -- name: Create initial host groups for all hosts -  hosts: l_oo_all_hosts -  gather_facts: no -  tags: -  - always -  tasks: -  - include_vars: ../byo/openshift-cluster/cluster_hosts.yml - -- include: ../common/openshift-cluster/evaluate_groups.yml  - name: Gather Cluster facts    hosts: OSEv3 diff --git a/playbooks/byo/rhel_subscribe.yml b/playbooks/byo/rhel_subscribe.yml index 8e7568e33..65c0b1c01 100644 --- a/playbooks/byo/rhel_subscribe.yml +++ b/playbooks/byo/rhel_subscribe.yml @@ -1,29 +1,7 @@  --- -- name: Create initial host groups for localhost -  hosts: localhost -  connection: local -  become: no -  gather_facts: no +- include: ../common/openshift-cluster/std_include.yml    tags:    - always -  tasks: -  - include_vars: openshift-cluster/cluster_hosts.yml -  - name: Evaluate group l_oo_all_hosts -    add_host: -      name: "{{ item }}" -      groups: l_oo_all_hosts -    with_items: "{{ g_all_hosts | default([]) }}" -    changed_when: False - -- name: Create initial host groups for all hosts -  hosts: l_oo_all_hosts -  gather_facts: no -  tags: -  - always -  tasks: -  - include_vars: ../byo/openshift-cluster/cluster_hosts.yml - -- include: ../common/openshift-cluster/evaluate_groups.yml  - name: Subscribe hosts, update repos and update OS packages    hosts: l_oo_all_hosts diff --git a/playbooks/common/openshift-cluster/config.yml b/playbooks/common/openshift-cluster/config.yml index a95cb68b7..a0ba735ab 100644 --- a/playbooks/common/openshift-cluster/config.yml +++ b/playbooks/common/openshift-cluster/config.yml @@ -1,20 +1,4 @@  --- -- include: evaluate_groups.yml -  tags: -  - always - -- include: initialize_facts.yml -  tags: -  - always - -- include: validate_hostnames.yml -  tags: -  - node - -- include: initialize_openshift_version.yml -  tags: -  - always -  - name: Set oo_option facts    hosts: oo_all_hosts    tags: diff --git a/playbooks/common/openshift-cluster/openshift_hosted.yml b/playbooks/common/openshift-cluster/openshift_hosted.yml index 143bc37a2..3c4a99887 100644 --- a/playbooks/common/openshift-cluster/openshift_hosted.yml +++ b/playbooks/common/openshift-cluster/openshift_hosted.yml @@ -54,6 +54,7 @@    - set_fact:        logging_hostname: "{{ openshift_hosted_logging_hostname | default('kibana.' ~ (openshift_master_default_subdomain | default('router.default.svc.cluster.local', true))) }}"    tasks: +    - block:      - include_role:          name: openshift_hosted_logging diff --git a/playbooks/common/openshift-cluster/openshift_logging.yml b/playbooks/common/openshift-cluster/openshift_logging.yml index 82f18f5e1..d96a78c4c 100644 --- a/playbooks/common/openshift-cluster/openshift_logging.yml +++ b/playbooks/common/openshift-cluster/openshift_logging.yml @@ -7,7 +7,8 @@  - name: Update Master configs    hosts: masters:!oo_first_master    tasks: -  - include_role: -      name: openshift_logging -      tasks_from: update_master_config +  - block: +    - include_role: +        name: openshift_logging +        tasks_from: update_master_config      when: openshift_logging_install_logging | default(false) | bool diff --git a/playbooks/common/openshift-cluster/redeploy-certificates.yml b/playbooks/common/openshift-cluster/redeploy-certificates.yml deleted file mode 100644 index a0e3f1d8a..000000000 --- a/playbooks/common/openshift-cluster/redeploy-certificates.yml +++ /dev/null @@ -1,250 +0,0 @@ ---- -- include: evaluate_groups.yml - -- include: initialize_facts.yml - -- include: initialize_openshift_version.yml - -- name: Load openshift_facts -  hosts: oo_etcd_to_config:oo_masters_to_config:oo_nodes_to_config -  roles: -  - openshift_facts - -- name: Redeploy etcd certificates -  hosts: oo_etcd_to_config -  any_errors_fatal: true -  vars: -    etcd_ca_host: "{{ groups.oo_etcd_to_config.0 }}" -    etcd_conf_dir: /etc/etcd -    etcd_generated_certs_dir: "{{ etcd_conf_dir }}/generated_certs" - -  pre_tasks: -  - stat: -      path: "{{ etcd_generated_certs_dir }}" -    register: etcd_generated_certs_dir_stat -  - name: Backup etcd certificates -    command: > -      tar -czvf /etc/etcd/etcd-certificate-backup-{{ ansible_date_time.epoch }}.tgz -      {{ etcd_conf_dir }}/ca.crt -      {{ etcd_conf_dir }}/ca -      {{ etcd_generated_certs_dir }} -    when: etcd_generated_certs_dir_stat.stat.exists -    delegate_to: "{{ etcd_ca_host }}" -    run_once: true -  - name: Remove existing etcd certificates -    file: -      path: "{{ item }}" -      state: absent -    with_items: -    - "{{ etcd_conf_dir }}/ca.crt" -    - "{{ etcd_conf_dir }}/ca" -    - "{{ etcd_generated_certs_dir }}" -  roles: -  - role: openshift_etcd_server_certificates -    etcd_peers: "{{ groups.oo_etcd_to_config | default([], true) }}" -    etcd_certificates_etcd_hosts: "{{ groups.oo_etcd_to_config | default([], true) }}" -    etcd_certificates_redeploy: true - -- name: Redeploy master certificates -  hosts: oo_masters_to_config -  any_errors_fatal: true -  vars: -    openshift_ca_host: "{{ groups.oo_first_master.0 }}" -    openshift_master_count: "{{ openshift.master.master_count | default(groups.oo_masters | length) }}" -  pre_tasks: -  # set_fact task copied from playbooks/common/openshift-master/config.yml -  # so that openshift_master_default_subdomain has a default value of "" -  # (emptry string). openshift_master_default_subdomain must have a default -  # value for openshift_master_facts to set metrics_public_url. -  # TODO: clean this up. -  - set_fact: -      openshift_master_default_subdomain: "{{ lookup('oo_option', 'openshift_master_default_subdomain') | default(None, true) }}" -    when: openshift_master_default_subdomain is not defined -  - stat: -      path: "{{ openshift_generated_configs_dir }}" -    register: openshift_generated_configs_dir_stat -  - name: Backup generated certificate and config directories -    command: > -      tar -czvf /etc/origin/master-node-cert-config-backup-{{ ansible_date_time.epoch }}.tgz -      {{ openshift_generated_configs_dir }} -      {{ openshift.common.config_base }}/master -    when: openshift_generated_configs_dir_stat.stat.exists -    delegate_to: "{{ openshift_ca_host }}" -    run_once: true -  - name: Remove generated certificate directories -    file: -      path: "{{ item }}" -      state: absent -    with_items: -    - "{{ openshift_generated_configs_dir }}" -  - name: Remove generated certificates -    file: -      path: "{{ openshift.common.config_base }}/master/{{ item }}" -      state: absent -    with_items: -    - "{{ hostvars[inventory_hostname] | certificates_to_synchronize(include_keys=false) }}" -    - "etcd.server.crt" -    - "etcd.server.key" -    - "master.etcd-client.crt" -    - "master.etcd-client.key" -    - "master.server.crt" -    - "master.server.key" -    - "openshift-master.crt" -    - "openshift-master.key" -    - "openshift-master.kubeconfig" -  - name: Remove CA certificate -    file: -      path: "{{ openshift.common.config_base }}/master/{{ item }}" -      state: absent -    when: openshift_certificates_redeploy_ca | default(false) | bool -    with_items: -    - "ca.crt" -    - "ca.key" -    - "ca.serial.txt" -    - "ca-bundle.crt" -  roles: -  - role: openshift_master_certificates -    openshift_master_etcd_hosts: "{{ hostvars -                                     | oo_select_keys(groups['oo_etcd_to_config'] | default([])) -                                     | oo_collect('openshift.common.hostname') -                                     | default(none, true) }}" -    openshift_certificates_redeploy: true -  - role: openshift_etcd_client_certificates -    etcd_certificates_redeploy: true -    etcd_ca_host: "{{ groups.oo_etcd_to_config.0 }}" -    etcd_cert_subdir: "openshift-master-{{ openshift.common.hostname }}" -    etcd_cert_config_dir: "{{ openshift.common.config_base }}/master" -    etcd_cert_prefix: "master.etcd-" -    when: groups.oo_etcd_to_config is defined and groups.oo_etcd_to_config - -- name: Redeploy node certificates -  hosts: oo_nodes_to_config -  any_errors_fatal: true -  pre_tasks: -  - name: Remove CA certificate -    file: -      path: "{{ item }}" -      state: absent -    with_items: -    - "{{ openshift.common.config_base }}/node/ca.crt" -  roles: -  - role: openshift_node_certificates -    openshift_node_master_api_url: "{{ hostvars[groups.oo_first_master.0].openshift.master.api_url }}" -    openshift_ca_host: "{{ groups.oo_first_master.0 }}" -    openshift_certificates_redeploy: true - -- name: Restart etcd -  hosts: oo_etcd_to_config -  tasks: -  - name: restart etcd -    service: -      name: "{{ 'etcd' if not openshift.common.is_containerized | bool else 'etcd_container' }}" -      state: restarted - -- name: Stop master services -  hosts: oo_masters_to_config -  vars: -    openshift_master_ha: "{{ groups.oo_masters_to_config | length > 1 }}" -  tasks: -  - name: stop master -    service: name={{ openshift.common.service_type }}-master state=stopped -    when: not openshift_master_ha | bool -  - name: stop master api -    service: name={{ openshift.common.service_type }}-master-api state=stopped -    when: openshift_master_ha | bool and openshift_master_cluster_method == 'native' -  - name: stop master controllers -    service: name={{ openshift.common.service_type }}-master-controllers state=stopped -    when: openshift_master_ha | bool and openshift_master_cluster_method == 'native' - -- name: Start master services -  hosts: oo_masters_to_config -  serial: 1 -  vars: -    openshift_master_ha: "{{ groups.oo_masters_to_config | length > 1 }}" -  tasks: -  - name: start master -    service: name={{ openshift.common.service_type }}-master state=started -    when: not openshift_master_ha | bool -  - name: start master api -    service: name={{ openshift.common.service_type }}-master-api state=started -    when: openshift_master_ha | bool and openshift_master_cluster_method == 'native' -  - name: start master controllers -    service: name={{ openshift.common.service_type }}-master-controllers state=started -    when: openshift_master_ha | bool and openshift_master_cluster_method == 'native' - -- name: Restart masters (pacemaker) -  hosts: oo_first_master -  vars: -    openshift_master_ha: "{{ groups.oo_masters_to_config | length > 1 }}" -  tasks: -  - name: restart master -    command: pcs resource restart master -    when: openshift_master_ha | bool and openshift_master_cluster_method == 'pacemaker' - -- name: Restart nodes -  hosts: oo_nodes_to_config -  tasks: -  - name: restart node -    service: name={{ openshift.common.service_type }}-node state=restarted - -- name: Copy admin client config(s) -  hosts: oo_first_master -  tasks: -  - name: Create temp directory for kubeconfig -    command: mktemp -d /tmp/openshift-ansible-XXXXXX -    register: mktemp -    changed_when: False - -  - name: Copy admin client config(s) -    command: > -      cp {{ openshift.common.config_base }}/master//admin.kubeconfig {{ mktemp.stdout }}/admin.kubeconfig -    changed_when: False - -- name: Serially drain all nodes to trigger redeployments -  hosts: oo_nodes_to_config -  serial: 1 -  any_errors_fatal: true -  tasks: -  - name: Determine if node is currently scheduleable -    command: > -      {{ openshift.common.client_binary }} --config={{ hostvars[groups.oo_first_master.0].mktemp.stdout }}/admin.kubeconfig -      get node {{ openshift.node.nodename }} -o json -    register: node_output -    when: openshift_certificates_redeploy_ca | default(false) | bool -    delegate_to: "{{ groups.oo_first_master.0 }}" -    changed_when: false - -  - set_fact: -      was_schedulable: "{{ 'unschedulable' not in (node_output.stdout | from_json).spec }}" -    when: openshift_certificates_redeploy_ca | default(false) | bool - -  - name: Prepare for node draining -    command: > -      {{ openshift.common.client_binary }} adm --config={{ hostvars[groups.oo_first_master.0].mktemp.stdout }}/admin.kubeconfig -      manage-node {{ openshift.node.nodename }} -      --schedulable=false -    delegate_to: "{{ groups.oo_first_master.0 }}" -    when: openshift_certificates_redeploy_ca | default(false) | bool and was_schedulable | bool - -  - name: Drain node -    command: > -      {{ openshift.common.admin_binary }} --config={{ hostvars[groups.oo_first_master.0].mktemp.stdout }}/admin.kubeconfig -      drain {{ openshift.node.nodename }} --force --delete-local-data -    delegate_to: "{{ groups.oo_first_master.0 }}" -    when: openshift_certificates_redeploy_ca | default(false) | bool and was_schedulable | bool - -  - name: Set node schedulability -    command: > -      {{ openshift.common.client_binary }} adm --config={{ hostvars[groups.oo_first_master.0].mktemp.stdout }}/admin.kubeconfig -      manage-node {{ openshift.node.nodename }} --schedulable=true -    delegate_to: "{{ groups.oo_first_master.0 }}" -    when: openshift_certificates_redeploy_ca | default(false) | bool and was_schedulable | bool - -- name: Delete temporary directory -  hosts: oo_first_master -  tasks: -  - name: Delete temp directory -    file: -      name: "{{ mktemp.stdout }}" -      state: absent -    changed_when: False diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/ca.yml b/playbooks/common/openshift-cluster/redeploy-certificates/ca.yml new file mode 100644 index 000000000..0b1c39ba4 --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/ca.yml @@ -0,0 +1,353 @@ +--- +- name: Verify OpenShift version is greater than or equal to 1.2 or 3.2 +  hosts: oo_first_master +  tasks: +  - fail: +      msg: "The current OpenShift version is less than 1.2/3.2 and does not support CA bundles." +    when: not openshift.common.version_gte_3_2_or_1_2 | bool + +- name: Backup existing etcd CA certificate directories +  hosts: oo_etcd_to_config +  roles: +  - etcd_common +  tasks: +  - name: Determine if CA certificate directory exists +    stat: +      path: "{{ etcd_ca_dir }}" +    register: etcd_ca_certs_dir_stat +  - name: Backup generated etcd certificates +    command: > +      tar -czf {{ etcd_conf_dir }}/etcd-ca-certificate-backup-{{ ansible_date_time.epoch }}.tgz +      {{ etcd_ca_dir }} +    args: +      warn: no +    when: etcd_ca_certs_dir_stat.stat.exists | bool +  - name: Remove CA certificate directory +    file: +      path: "{{ etcd_ca_dir }}" +      state: absent +    when: etcd_ca_certs_dir_stat.stat.exists | bool + +- name: Generate new etcd CA +  hosts: oo_first_etcd +  roles: +  - role: etcd_ca +    etcd_peers: "{{ groups.oo_etcd_to_config | default([], true) }}" +    etcd_ca_host: "{{ groups.oo_etcd_to_config.0 }}" +    etcd_certificates_etcd_hosts: "{{ groups.oo_etcd_to_config | default([], true) }}" + +- name: Create temp directory for syncing certs +  hosts: localhost +  connection: local +  become: no +  gather_facts: no +  tasks: +  - name: Create local temp directory for syncing certs +    local_action: command mktemp -d /tmp/openshift-ansible-XXXXXXX +    register: g_etcd_mktemp +    changed_when: false + +- name: Distribute etcd CA to etcd hosts +  hosts: oo_etcd_to_config +  vars: +    etcd_ca_host: "{{ groups.oo_etcd_to_config.0 }}" +  roles: +  - etcd_common +  tasks: +  - name: Create a tarball of the etcd ca certs +    command: > +      tar -czvf {{ etcd_conf_dir }}/{{ etcd_ca_name }}.tgz +        -C {{ etcd_ca_dir }} . +    args: +      creates: "{{ etcd_conf_dir }}/{{ etcd_ca_name }}.tgz" +      warn: no +    delegate_to: "{{ etcd_ca_host }}" +    run_once: true +  - name: Retrieve etcd ca cert tarball +    fetch: +      src: "{{ etcd_conf_dir }}/{{ etcd_ca_name }}.tgz" +      dest: "{{ hostvars['localhost'].g_etcd_mktemp.stdout }}/" +      flat: yes +      fail_on_missing: yes +      validate_checksum: yes +    delegate_to: "{{ etcd_ca_host }}" +    run_once: true +  - name: Ensure ca directory exists +    file: +      path: "{{ etcd_ca_dir }}" +      state: directory +  - name: Unarchive etcd ca cert tarballs +    unarchive: +      src: "{{ hostvars['localhost'].g_etcd_mktemp.stdout }}/{{ etcd_ca_name }}.tgz" +      dest: "{{ etcd_ca_dir }}" +  - name: Read current etcd CA +    slurp: +      src: "{{ etcd_conf_dir }}/ca.crt" +    register: g_current_etcd_ca_output +  - name: Read new etcd CA +    slurp: +      src: "{{ etcd_ca_dir }}/ca.crt" +    register: g_new_etcd_ca_output +  - copy: +      content: "{{ (g_new_etcd_ca_output.content|b64decode) + (g_current_etcd_ca_output.content|b64decode) }}" +      dest: "{{ item }}/ca.crt" +    with_items: +    - "{{ etcd_conf_dir }}" +    - "{{ etcd_ca_dir }}" + +- name: Retrieve etcd CA certificate +  hosts: oo_first_etcd +  roles: +  - etcd_common +  tasks: +  - name: Retrieve etcd CA certificate +    fetch: +      src: "{{ etcd_conf_dir }}/ca.crt" +      dest: "{{ hostvars['localhost'].g_etcd_mktemp.stdout }}/" +      flat: yes +      fail_on_missing: yes +      validate_checksum: yes + +- name: Distribute etcd CA to masters +  hosts: oo_masters_to_config +  vars: +    openshift_ca_host: "{{ groups.oo_first_master.0 }}" +  tasks: +  - name: Deploy CA certificate, key, bundle and serial +    copy: +      src: "{{ hostvars['localhost'].g_etcd_mktemp.stdout }}/ca.crt" +      dest: "{{ openshift.common.config_base }}/master/master.etcd-ca.crt" +    when: groups.oo_etcd_to_config | default([]) | length > 0 + +- name: Delete temporary directory on localhost +  hosts: localhost +  connection: local +  become: no +  gather_facts: no +  tasks: +  - file: +      name: "{{ g_etcd_mktemp.stdout }}" +      state: absent +    changed_when: false + +- include: ../../../common/openshift-etcd/restart.yml + +# Update master config when ca-bundle not referenced. Services will be +# restarted below after new CA certificate has been distributed. +- name: Ensure ca-bundle.crt is referenced in master configuration +  hosts: oo_masters_to_config +  tasks: +  - slurp: +      src: "{{ openshift.common.config_base }}/master/master-config.yaml" +    register: g_master_config_output +  - modify_yaml: +      dest: "{{ openshift.common.config_base }}/master/master-config.yaml" +      yaml_key: kubeletClientInfo.ca +      yaml_value: ca-bundle.crt +    when: (g_master_config_output.content|b64decode|from_yaml).kubeletClientInfo.ca != 'ca-bundle.crt' +  - modify_yaml: +      dest: "{{ openshift.common.config_base }}/master/master-config.yaml" +      yaml_key: serviceAccountConfig.masterCA +      yaml_value: ca-bundle.crt +    when: (g_master_config_output.content|b64decode|from_yaml).serviceAccountConfig.masterCA != 'ca-bundle.crt' +  - modify_yaml: +      dest: "{{ openshift.common.config_base }}/master/master-config.yaml" +      yaml_key: oauthConfig.masterCA +      yaml_value: ca-bundle.crt +    when: (g_master_config_output.content|b64decode|from_yaml).oauthConfig.masterCA != 'ca-bundle.crt' +  - modify_yaml: +      dest: "{{ openshift.common.config_base }}/master/master-config.yaml" +      yaml_key: servingInfo.clientCA +      yaml_value: ca-bundle.crt +    when: (g_master_config_output.content|b64decode|from_yaml).servingInfo.clientCA != 'ca-bundle.crt' + +- name: Copy current OpenShift CA to legacy directory +  hosts: oo_masters_to_config +  pre_tasks: +  - name: Create legacy-ca directory +    file: +      path: "{{ openshift.common.config_base }}/master/legacy-ca" +      state: directory +      mode: 0700 +      owner: root +      group: root +  - command: mktemp -u XXXXXX +    register: g_legacy_ca_mktemp +    changed_when: false +  # Copy CA certificate, key, serial and bundle to legacy-ca with a +  # prefix generated by mktemp, ie. XXXXXX-ca.crt. +  # +  # The following roles will pick up all CA certificates matching +  # /.*-ca.crt/ in the legacy-ca directory and ensure they are present +  # in the OpenShift CA bundle. +  # - openshift_ca +  # - openshift_master_certificates +  # - openshift_node_certificates +  - name: Copy current OpenShift CA to legacy directory +    copy: +      src: "{{ openshift.common.config_base }}/master/{{ item }}" +      dest: "{{ openshift.common.config_base }}/master/legacy-ca/{{ g_legacy_ca_mktemp.stdout }}-{{ item }}" +      remote_src: true +    # It is possible that redeploying failed and files may be missing. +    # Ignore errors in this case. Files should have been copied to +    # legacy-ca directory in previous run. +    ignore_errors: true +    with_items: +    - "ca.crt" +    - "ca.key" +    - "ca.serial.txt" +    - "ca-bundle.crt" + +- name: Generate new OpenShift CA certificate +  hosts: oo_first_master +  pre_tasks: +  - name: Create temporary directory for creating new CA certificate +    command: > +      mktemp -d /tmp/openshift-ansible-XXXXXXX +    register: g_new_openshift_ca_mktemp +    changed_when: false +  roles: +  - role: openshift_ca +    # Set openshift_ca_config_dir to a temporary directory where CA +    # will be created. We'll replace the existing CA with the CA +    # created in the temporary directory. +    openshift_ca_config_dir: "{{ g_new_openshift_ca_mktemp.stdout }}" +    openshift_ca_host: "{{ groups.oo_first_master.0 }}" +    openshift_master_hostnames: "{{ hostvars +                                    | oo_select_keys(groups['oo_masters_to_config'] | default([])) +                                    | oo_collect('openshift.common.all_hostnames') +                                    | oo_flatten | unique }}" + +- name: Create temp directory for syncing certs +  hosts: localhost +  connection: local +  become: no +  gather_facts: no +  tasks: +  - name: Create local temp directory for syncing certs +    local_action: command mktemp -d /tmp/openshift-ansible-XXXXXXX +    register: g_master_mktemp +    changed_when: false + +- name: Retrieve OpenShift CA +  hosts: oo_first_master +  vars: +    openshift_ca_host: "{{ groups.oo_first_master.0 }}" +  tasks: +  - name: Retrieve CA certificate, key, bundle and serial +    fetch: +      src: "{{ hostvars[openshift_ca_host].g_new_openshift_ca_mktemp.stdout }}/{{ item }}" +      dest: "{{ hostvars['localhost'].g_master_mktemp.stdout }}/" +      flat: yes +      fail_on_missing: yes +      validate_checksum: yes +    with_items: +    - ca.crt +    - ca.key +    - ca-bundle.crt +    - ca.serial.txt +    delegate_to: "{{ openshift_ca_host }}" +    run_once: true +    changed_when: false + +- name: Distribute OpenShift CA to masters +  hosts: oo_masters_to_config +  vars: +    openshift_ca_host: "{{ groups.oo_first_master.0 }}" +  tasks: +  - name: Deploy CA certificate, key, bundle and serial +    copy: +      src: "{{ hostvars['localhost'].g_master_mktemp.stdout }}/{{ item }}" +      dest: "{{ openshift.common.config_base }}/master/" +    with_items: +    - ca.crt +    - ca.key +    - ca-bundle.crt +    - ca.serial.txt +  - name: Update master client kubeconfig CA data +    kubeclient_ca: +      client_path: "{{ openshift.common.config_base }}/master/openshift-master.kubeconfig" +      ca_path: "{{ openshift.common.config_base }}/master/ca-bundle.crt" +  - name: Update admin client kubeconfig CA data +    kubeclient_ca: +      client_path: "{{ openshift.common.config_base }}/master/admin.kubeconfig" +      ca_path: "{{ openshift.common.config_base }}/master/ca-bundle.crt" +  - name: Lookup default group for ansible_ssh_user +    command: "/usr/bin/id -g {{ ansible_ssh_user }}" +    changed_when: false +    register: _ansible_ssh_user_gid +  - set_fact: +      client_users: "{{ [ansible_ssh_user, 'root'] | unique }}" +  - name: Create the client config dir(s) +    file: +      path: "~{{ item }}/.kube" +      state: directory +      mode: 0700 +      owner: "{{ item }}" +      group: "{{ 'root' if item == 'root' else _ansible_ssh_user_gid.stdout  }}" +    with_items: "{{ client_users }}" +  - name: Copy the admin client config(s) +    copy: +      src: "{{ openshift.common.config_base }}/master/admin.kubeconfig" +      dest: "~{{ item }}/.kube/config" +      remote_src: yes +    with_items: "{{ client_users }}" +  - name: Update the permissions on the admin client config(s) +    file: +      path: "~{{ item }}/.kube/config" +      state: file +      mode: 0700 +      owner: "{{ item }}" +      group: "{{ 'root' if item == 'root' else _ansible_ssh_user_gid.stdout  }}" +    with_items: "{{ client_users }}" + +- include: ../../../common/openshift-master/restart.yml + +- name: Distribute OpenShift CA certificate to nodes +  hosts: oo_nodes_to_config +  vars: +    openshift_ca_host: "{{ groups.oo_first_master.0 }}" +  tasks: +  - copy: +      src: "{{ hostvars['localhost'].g_master_mktemp.stdout }}/ca-bundle.crt" +      dest: "{{ openshift.common.config_base }}/node/ca.crt" +  - name: Copy OpenShift CA to system CA trust +    copy: +      src: "{{ item.cert }}" +      dest: "/etc/pki/ca-trust/source/anchors/{{ item.id }}-{{ item.cert | basename }}" +      remote_src: yes +    with_items: +    - id: openshift +      cert: "{{ openshift.common.config_base }}/node/ca.crt" +    notify: +    - update ca trust +  - name: Update node client kubeconfig CA data +    kubeclient_ca: +      client_path: "{{ openshift.common.config_base }}/node/system:node:{{ openshift.common.hostname }}.kubeconfig" +      ca_path: "{{ openshift.common.config_base }}/node/ca.crt" +  handlers: +  # Normally this handler would restart docker after updating ca +  # trust. We'll do that when we restart nodes to avoid restarting +  # docker on all nodes in parallel. +  - name: update ca trust +    command: update-ca-trust + +- name: Delete temporary directory on CA host +  hosts: oo_first_master +  tasks: +  - file: +      path: "{{ g_new_openshift_ca_mktemp.stdout }}" +      state: absent + +- name: Delete temporary directory on localhost +  hosts: localhost +  connection: local +  become: no +  gather_facts: no +  tasks: +  - file: +      name: "{{ g_master_mktemp.stdout }}" +      state: absent +    changed_when: false + +- include: ../../../common/openshift-node/restart.yml diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/etcd.yml b/playbooks/common/openshift-cluster/redeploy-certificates/etcd.yml new file mode 100644 index 000000000..2963a5940 --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/etcd.yml @@ -0,0 +1,66 @@ +--- +- name: Backup and remove generated etcd certificates +  hosts: oo_first_etcd +  any_errors_fatal: true +  roles: +    - etcd_common +  post_tasks: +    - name: Determine if generated etcd certificates exist +      stat: +        path: "{{ etcd_conf_dir }}/generated_certs" +      register: etcd_generated_certs_dir_stat +    - name: Backup generated etcd certificates +      command: > +        tar -czf {{ etcd_conf_dir }}/etcd-generated-certificate-backup-{{ ansible_date_time.epoch }}.tgz +        {{ etcd_conf_dir }}/generated_certs +      args: +        warn: no +      when: etcd_generated_certs_dir_stat.stat.exists | bool +    - name: Remove generated etcd certificates +      file: +        path: "{{ item }}" +        state: absent +      with_items: +        - "{{ etcd_conf_dir }}/generated_certs" + +- name: Backup and removed deployed etcd certificates +  hosts: oo_etcd_to_config +  any_errors_fatal: true +  roles: +    - etcd_common +  post_tasks: +    - name: Backup etcd certificates +      command: > +        tar -czvf /etc/etcd/etcd-server-certificate-backup-{{ ansible_date_time.epoch }}.tgz +        {{ etcd_conf_dir }}/ca.crt +        {{ etcd_conf_dir }}/server.crt +        {{ etcd_conf_dir }}/server.key +        {{ etcd_conf_dir }}/peer.crt +        {{ etcd_conf_dir }}/peer.key +      args: +        warn: no + +- name: Redeploy etcd certificates +  hosts: oo_etcd_to_config +  any_errors_fatal: true +  roles: +    - role: openshift_etcd_server_certificates +      etcd_certificates_redeploy: true +      etcd_ca_host: "{{ groups.oo_etcd_to_config.0 }}" +      etcd_peers: "{{ groups.oo_etcd_to_config | default([], true) }}" +      etcd_certificates_etcd_hosts: "{{ groups.oo_etcd_to_config | default([], true) }}" +      openshift_ca_host: "{{ groups.oo_first_master.0 }}" + +- name: Redeploy etcd client certificates for masters +  hosts: oo_masters_to_config +  any_errors_fatal: true +  roles: +    - role: openshift_etcd_client_certificates +      etcd_certificates_redeploy: true +      etcd_ca_host: "{{ groups.oo_etcd_to_config.0 }}" +      etcd_cert_subdir: "openshift-master-{{ openshift.common.hostname }}" +      etcd_cert_config_dir: "{{ openshift.common.config_base }}/master" +      etcd_cert_prefix: "master.etcd-" +      openshift_ca_host: "{{ groups.oo_first_master.0 }}" +      openshift_master_count: "{{ openshift.master.master_count | default(groups.oo_masters | length) }}" +      when: groups.oo_etcd_to_config is defined and groups.oo_etcd_to_config diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/filter_plugins b/playbooks/common/openshift-cluster/redeploy-certificates/filter_plugins new file mode 120000 index 000000000..b1213dedb --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/filter_plugins @@ -0,0 +1 @@ +../../../../filter_plugins
\ No newline at end of file diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/library b/playbooks/common/openshift-cluster/redeploy-certificates/library new file mode 120000 index 000000000..9a53f009d --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/library @@ -0,0 +1 @@ +../../../../library
\ No newline at end of file diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/lookup_plugins b/playbooks/common/openshift-cluster/redeploy-certificates/lookup_plugins new file mode 120000 index 000000000..aff753026 --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/lookup_plugins @@ -0,0 +1 @@ +../../../../lookup_plugins
\ No newline at end of file diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/masters.yml b/playbooks/common/openshift-cluster/redeploy-certificates/masters.yml new file mode 100644 index 000000000..f653a111f --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/masters.yml @@ -0,0 +1,45 @@ +--- +- name: Redeploy master certificates +  hosts: oo_masters_to_config +  any_errors_fatal: true +  vars: +    openshift_ca_host: "{{ groups.oo_first_master.0 }}" +    openshift_master_count: "{{ openshift.master.master_count | default(groups.oo_masters | length) }}" +  pre_tasks: +  - stat: +      path: "{{ openshift_generated_configs_dir }}" +    register: openshift_generated_configs_dir_stat +  - name: Backup generated certificate and config directories +    command: > +      tar -czvf /etc/origin/master-node-cert-config-backup-{{ ansible_date_time.epoch }}.tgz +      {{ openshift_generated_configs_dir }} +      {{ openshift.common.config_base }}/master +    when: openshift_generated_configs_dir_stat.stat.exists +    delegate_to: "{{ openshift_ca_host }}" +    run_once: true +  - name: Remove generated certificate directories +    file: +      path: "{{ item }}" +      state: absent +    with_items: +    - "{{ openshift_generated_configs_dir }}" +  - name: Remove generated certificates +    file: +      path: "{{ openshift.common.config_base }}/master/{{ item }}" +      state: absent +    with_items: +    - "{{ hostvars[inventory_hostname] | certificates_to_synchronize(include_keys=false, include_ca=false) }}" +    - "etcd.server.crt" +    - "etcd.server.key" +    - "master.server.crt" +    - "master.server.key" +    - "openshift-master.crt" +    - "openshift-master.key" +    - "openshift-master.kubeconfig" +  roles: +  - role: openshift_master_certificates +    openshift_master_etcd_hosts: "{{ hostvars +                                     | oo_select_keys(groups['oo_etcd_to_config'] | default([])) +                                     | oo_collect('openshift.common.hostname') +                                     | default(none, true) }}" +    openshift_certificates_redeploy: true diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/nodes.yml b/playbooks/common/openshift-cluster/redeploy-certificates/nodes.yml new file mode 100644 index 000000000..4990a03f2 --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/nodes.yml @@ -0,0 +1,29 @@ +--- +- name: Ensure node directory is absent from generated configs +  hosts: oo_first_master +  tasks: +  # The generated configs directory (/etc/origin/generated-configs) is +  # backed up during redeployment of the control plane certificates. +  # We need to ensure that the generated config directory for +  # individual nodes has been deleted before continuing, so verify +  # that it is missing here. +  - name: Ensure node directories and tarballs are absent from generated configs +    shell: > +      rm -rf {{ openshift.common.config_base }}/generated-configs/node-* +    args: +      warn: no + +- name: Redeploy node certificates +  hosts: oo_nodes_to_config +  pre_tasks: +  - name: Remove CA certificate +    file: +      path: "{{ item }}" +      state: absent +    with_items: +    - "{{ openshift.common.config_base }}/node/ca.crt" +  roles: +  - role: openshift_node_certificates +    openshift_node_master_api_url: "{{ hostvars[groups.oo_first_master.0].openshift.master.api_url }}" +    openshift_ca_host: "{{ groups.oo_first_master.0 }}" +    openshift_certificates_redeploy: true diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/registry.yml b/playbooks/common/openshift-cluster/redeploy-certificates/registry.yml new file mode 100644 index 000000000..18b93e1d6 --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/registry.yml @@ -0,0 +1,93 @@ +--- +- name: Update registry certificates +  hosts: oo_first_master +  vars: +  tasks: +  - name: Create temp directory for kubeconfig +    command: mktemp -d /tmp/openshift-ansible-XXXXXX +    register: mktemp +    changed_when: false + +  - name: Copy admin client config(s) +    command: > +      cp {{ openshift.common.config_base }}/master//admin.kubeconfig {{ mktemp.stdout }}/admin.kubeconfig +    changed_when: false + +  - name: Determine if docker-registry exists +    command: > +      {{ openshift.common.client_binary }} get dc/docker-registry -o json +      --config={{ mktemp.stdout }}/admin.kubeconfig +      -n default +    register: l_docker_registry_dc +    failed_when: false +    changed_when: false + +  - set_fact: +      docker_registry_env_vars: "{{ ((l_docker_registry_dc.stdout | from_json)['spec']['template']['spec']['containers'][0]['env'] +                                      | oo_collect('name')) +                                      | default([]) }}" +      docker_registry_secrets: "{{ ((l_docker_registry_dc.stdout | from_json)['spec']['template']['spec']['volumes'] +                                     | oo_collect('secret') +                                     | oo_collect('secretName')) +                                     | default([]) }}" +    changed_when: false +    when: l_docker_registry_dc.rc == 0 + +  # Replace dc/docker-registry environment variable certificate data if set. +  - name: Update docker-registry environment variables +    shell: > +      {{ openshift.common.client_binary }} env dc/docker-registry +      OPENSHIFT_CA_DATA="$(cat /etc/origin/master/ca.crt)" +      OPENSHIFT_CERT_DATA="$(cat /etc/origin/master/openshift-registry.crt)" +      OPENSHIFT_KEY_DATA="$(cat /etc/origin/master/openshift-registry.key)" +      --config={{ mktemp.stdout }}/admin.kubeconfig +      -n default +    when: l_docker_registry_dc.rc == 0 and 'OPENSHIFT_CA_DATA' in docker_registry_env_vars and 'OPENSHIFT_CERT_DATA' in docker_registry_env_vars and 'OPENSHIFT_KEY_DATA' in docker_registry_env_vars + +  # Replace dc/docker-registry certificate secret contents if set. +  - block: +    - name: Retrieve registry service IP +      command: > +        {{ openshift.common.client_binary }} get service docker-registry +        -o jsonpath='{.spec.clusterIP}' +        --config={{ mktemp.stdout }}/admin.kubeconfig +        -n default +      register: docker_registry_service_ip +      changed_when: false + +    - set_fact: +        docker_registry_route_hostname: "{{ 'docker-registry-default.' ~ (openshift.master.default_subdomain | default('router.default.svc.cluster.local', true)) }}" +      changed_when: false + +    - name: Generate registry certificate +      command: > +        {{ openshift.common.client_binary }} adm ca create-server-cert +        --signer-cert={{ openshift.common.config_base }}/master/ca.crt +        --signer-key={{ openshift.common.config_base }}/master/ca.key +        --signer-serial={{ openshift.common.config_base }}/master/ca.serial.txt +        --hostnames="{{ docker_registry_service_ip.stdout }},docker-registry.default.svc.cluster.local,{{ docker_registry_route_hostname }}" +        --cert={{ openshift.common.config_base }}/master/registry.crt +        --key={{ openshift.common.config_base }}/master/registry.key + +    - name: Update registry certificates secret +      shell: > +        {{ openshift.common.client_binary }} secret new registry-certificates +        {{ openshift.common.config_base }}/master/registry.crt +        {{ openshift.common.config_base }}/master/registry.key +        --config={{ mktemp.stdout }}/admin.kubeconfig +        -n default +        -o json | oc replace -f - +    when: l_docker_registry_dc.rc == 0 and 'registry-certificates' in docker_registry_secrets and 'REGISTRY_HTTP_TLS_CERTIFICATE' in docker_registry_env_vars and 'REGISTRY_HTTP_TLS_KEY' in docker_registry_env_vars + +  - name: Redeploy docker registry +    command: > +      {{ openshift.common.client_binary }} deploy dc/docker-registry +      --latest +      --config={{ mktemp.stdout }}/admin.kubeconfig +      -n default + +  - name: Delete temp directory +    file: +      name: "{{ mktemp.stdout }}" +      state: absent +    changed_when: False diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/roles b/playbooks/common/openshift-cluster/redeploy-certificates/roles new file mode 120000 index 000000000..4bdbcbad3 --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/roles @@ -0,0 +1 @@ +../../../../roles
\ No newline at end of file diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/router.yml b/playbooks/common/openshift-cluster/redeploy-certificates/router.yml new file mode 100644 index 000000000..a9e9f0915 --- /dev/null +++ b/playbooks/common/openshift-cluster/redeploy-certificates/router.yml @@ -0,0 +1,80 @@ +--- +- name: Update router certificates +  hosts: oo_first_master +  vars: +  tasks: +  - name: Create temp directory for kubeconfig +    command: mktemp -d /tmp/openshift-ansible-XXXXXX +    register: mktemp +    changed_when: false + +  - name: Copy admin client config(s) +    command: > +      cp {{ openshift.common.config_base }}/master//admin.kubeconfig {{ mktemp.stdout }}/admin.kubeconfig +    changed_when: false + +  - name: Determine if router exists +    command: > +      {{ openshift.common.client_binary }} get dc/router -o json +      --config={{ mktemp.stdout }}/admin.kubeconfig +      -n default +    register: l_router_dc +    failed_when: false +    changed_when: false + +  - set_fact: +      router_env_vars: "{{ ((l_router_dc.stdout | from_json)['spec']['template']['spec']['containers'][0]['env'] +                             | oo_collect('name')) +                             | default([]) }}" +      router_secrets: "{{ ((l_router_dc.stdout | from_json)['spec']['template']['spec']['volumes'] +                            | oo_collect('secret') +                            | oo_collect('secretName')) +                            | default([]) }}" +    changed_when: false +    when: l_router_dc.rc == 0 + +  - name: Update router environment variables +    shell: > +      {{ openshift.common.client_binary }} env dc/router +      OPENSHIFT_CA_DATA="$(cat /etc/origin/master/ca.crt)" +      OPENSHIFT_CERT_DATA="$(cat /etc/origin/master/openshift-router.crt)" +      OPENSHIFT_KEY_DATA="$(cat /etc/origin/master/openshift-router.key)" +      --config={{ mktemp.stdout }}/admin.kubeconfig +      -n default +    when: l_router_dc.rc == 0 and 'OPENSHIFT_CA_DATA' in router_env_vars and 'OPENSHIFT_CERT_DATA' in router_env_vars and 'OPENSHIFT_KEY_DATA' in router_env_vars + +  - block: +    - name: Delete existing router certificate secret +      command: > +        {{ openshift.common.client_binary }} delete secret/router-certs +        --config={{ mktemp.stdout }}/admin.kubeconfig +        -n default + +    - name: Remove router service annotations +      command: > +        {{ openshift.common.client_binary }} annotate service/router +        service.alpha.openshift.io/serving-cert-secret-name- +        service.alpha.openshift.io/serving-cert-signed-by- +        --config={{ mktemp.stdout }}/admin.kubeconfig +        -n default + +    - name: Add serving-cert-secret annotation to router service +      command: > +        {{ openshift.common.client_binary }} annotate service/router +        service.alpha.openshift.io/serving-cert-secret-name=router-certs +        --config={{ mktemp.stdout }}/admin.kubeconfig +        -n default +    when: l_router_dc.rc == 0 and 'router-certs' in router_secrets + +  - name: Redeploy router +    command: > +      {{ openshift.common.client_binary }} deploy dc/router +      --latest +      --config={{ mktemp.stdout }}/admin.kubeconfig +      -n default + +  - name: Delete temp directory +    file: +      name: "{{ mktemp.stdout }}" +      state: absent +    changed_when: False diff --git a/playbooks/common/openshift-cluster/std_include.yml b/playbooks/common/openshift-cluster/std_include.yml new file mode 100644 index 000000000..078991b12 --- /dev/null +++ b/playbooks/common/openshift-cluster/std_include.yml @@ -0,0 +1,42 @@ +--- +- name: Create initial host groups for localhost +  hosts: localhost +  connection: local +  become: no +  gather_facts: no +  tags: +  - always +  tasks: +  - include_vars: ../../byo/openshift-cluster/cluster_hosts.yml +  - name: Evaluate group l_oo_all_hosts +    add_host: +      name: "{{ item }}" +      groups: l_oo_all_hosts +    with_items: "{{ g_all_hosts | default([]) }}" +    changed_when: no + +- name: Create initial host groups for all hosts +  hosts: l_oo_all_hosts +  gather_facts: no +  tags: +  - always +  tasks: +  - include_vars: ../../byo/openshift-cluster/cluster_hosts.yml +  - set_fact: +      openshift_deployment_type: "{{ deployment_type }}" + +- include: evaluate_groups.yml +  tags: +  - always + +- include: initialize_facts.yml +  tags: +  - always + +- include: validate_hostnames.yml +  tags: +  - node + +- include: initialize_openshift_version.yml +  tags: +  - always diff --git a/playbooks/common/openshift-cluster/upgrades/upgrade_control_plane.yml b/playbooks/common/openshift-cluster/upgrades/upgrade_control_plane.yml index 9cad931af..db2c27919 100644 --- a/playbooks/common/openshift-cluster/upgrades/upgrade_control_plane.yml +++ b/playbooks/common/openshift-cluster/upgrades/upgrade_control_plane.yml @@ -229,3 +229,56 @@    tasks:    - include: docker/upgrade.yml      when: l_docker_upgrade is defined and l_docker_upgrade | bool and not openshift.common.is_atomic | bool + +- name: Drain and upgrade master nodes +  hosts: oo_masters_to_config:&oo_nodes_to_upgrade +  # This var must be set with -e on invocation, as it is not a per-host inventory var +  # and is evaluated early. Values such as "20%" can also be used. +  serial: "{{ openshift_upgrade_nodes_serial | default(1) }}" +  any_errors_fatal: true + +  pre_tasks: +  # TODO: To better handle re-trying failed upgrades, it would be nice to check if the node +  # or docker actually needs an upgrade before proceeding. Perhaps best to save this until +  # we merge upgrade functionality into the base roles and a normal config.yml playbook run. +  - name: Determine if node is currently scheduleable +    command: > +      {{ hostvars[groups.oo_first_master.0].openshift.common.client_binary }} get node {{ openshift.node.nodename | lower }} -o json +    register: node_output +    delegate_to: "{{ groups.oo_first_master.0 }}" +    changed_when: false + +  - set_fact: +      was_schedulable: "{{ 'unschedulable' not in (node_output.stdout | from_json).spec }}" + +  - name: Mark node unschedulable +    command: > +      {{ hostvars[groups.oo_first_master.0].openshift.common.client_binary }} adm manage-node {{ openshift.node.nodename | lower }} --schedulable=false +    delegate_to: "{{ groups.oo_first_master.0 }}" +    # NOTE: There is a transient "object has been modified" error here, allow a couple +    # retries for a more reliable upgrade. +    register: node_unsched +    until: node_unsched.rc == 0 +    retries: 3 +    delay: 1 + +  - name: Drain Node for Kubelet upgrade +    command: > +      {{ hostvars[groups.oo_first_master.0].openshift.common.admin_binary }} drain {{ openshift.node.nodename | lower }} --force --delete-local-data +    delegate_to: "{{ groups.oo_first_master.0 }}" + +  roles: +  - openshift_facts +  - docker +  - openshift_node_upgrade + +  post_tasks: +  - name: Set node schedulability +    command: > +      {{ hostvars[groups.oo_first_master.0].openshift.common.client_binary }} adm manage-node {{ openshift.node.nodename | lower }} --schedulable=true +    delegate_to: "{{ groups.oo_first_master.0 }}" +    when: was_schedulable | bool +    register: node_sched +    until: node_sched.rc == 0 +    retries: 3 +    delay: 1 diff --git a/playbooks/common/openshift-cluster/upgrades/upgrade_nodes.yml b/playbooks/common/openshift-cluster/upgrades/upgrade_nodes.yml index c0746a9e6..59188c570 100644 --- a/playbooks/common/openshift-cluster/upgrades/upgrade_nodes.yml +++ b/playbooks/common/openshift-cluster/upgrades/upgrade_nodes.yml @@ -1,6 +1,6 @@  ---  - name: Drain and upgrade nodes -  hosts: oo_nodes_to_upgrade +  hosts: oo_nodes_to_upgrade:!oo_masters_to_config    # This var must be set with -e on invocation, as it is not a per-host inventory var    # and is evaluated early. Values such as "20%" can also be used.    serial: "{{ openshift_upgrade_nodes_serial | default(1) }}" @@ -20,7 +20,7 @@    - set_fact:        was_schedulable: "{{ 'unschedulable' not in (node_output.stdout | from_json).spec }}" -  - name: Mark unschedulable if host is a node +  - name: Mark node unschedulable      command: >        {{ hostvars[groups.oo_first_master.0].openshift.common.client_binary }} adm manage-node {{ openshift.node.nodename | lower }} --schedulable=false      delegate_to: "{{ groups.oo_first_master.0 }}" diff --git a/playbooks/common/openshift-etcd/restart.yml b/playbooks/common/openshift-etcd/restart.yml new file mode 100644 index 000000000..196c86f28 --- /dev/null +++ b/playbooks/common/openshift-etcd/restart.yml @@ -0,0 +1,9 @@ +--- +- name: Restart etcd +  hosts: oo_etcd_to_config +  serial: 1 +  tasks: +    - name: restart etcd +      service: +        name: "{{ 'etcd' if not openshift.common.is_containerized | bool else 'etcd_container' }}" +        state: restarted diff --git a/playbooks/common/openshift-master/restart.yml b/playbooks/common/openshift-master/restart.yml new file mode 100644 index 000000000..b35368bf1 --- /dev/null +++ b/playbooks/common/openshift-master/restart.yml @@ -0,0 +1,19 @@ +--- +- include: ../../common/openshift-master/validate_restart.yml + +- name: Restart masters +  hosts: oo_masters_to_config +  vars: +    openshift_master_ha: "{{ groups.oo_masters_to_config | length > 1 }}" +  serial: 1 +  handlers: +  - include: roles/openshift_master/handlers/main.yml +    static: yes +  roles: +  - openshift_facts +  post_tasks: +  - include: ../../common/openshift-master/restart_hosts.yml +    when: openshift_rolling_restart_mode | default('services') == 'system' + +  - include: ../../common/openshift-master/restart_services.yml +    when: openshift_rolling_restart_mode | default('services') == 'services' diff --git a/playbooks/common/openshift-node/restart.yml b/playbooks/common/openshift-node/restart.yml new file mode 100644 index 000000000..6e9b1cca3 --- /dev/null +++ b/playbooks/common/openshift-node/restart.yml @@ -0,0 +1,47 @@ +--- +- name: Restart nodes +  hosts: oo_nodes_to_config +  serial: "{{ openshift_restart_nodes_serial | default(1) }}" +  tasks: +  - name: Restart docker +    service: name=docker state=restarted + +  - name: Update docker facts +    openshift_facts: +      role: docker + +  - name: Restart containerized services +    service: name={{ item }} state=started +    with_items: +    - etcd_container +    - openvswitch +    - "{{ openshift.common.service_type }}-master" +    - "{{ openshift.common.service_type }}-master-api" +    - "{{ openshift.common.service_type }}-master-controllers" +    - "{{ openshift.common.service_type }}-node" +    failed_when: false +    when: openshift.common.is_containerized | bool + +  - name: Wait for master API to come back online +    wait_for: +      host: "{{ openshift.common.hostname }}" +      state: started +      delay: 10 +      port: "{{ openshift.master.api_port }}" +    when: inventory_hostname in groups.oo_masters_to_config + +  - name: restart node +    service: +      name: "{{ openshift.common.service_type }}-node" +      state: restarted + +  - name: Wait for node to be ready +    command: > +      {{ hostvars[groups.oo_first_master.0].openshift.common.client_binary }} get node {{ openshift.common.hostname | lower }} --no-headers +    register: node_output +    delegate_to: "{{ groups.oo_first_master.0 }}" +    when: inventory_hostname in groups.oo_nodes_to_config +    until: "{{ node_output.stdout.split()[1].startswith('Ready')}}" +    # Give the node two minutes to come back online. +    retries: 24 +    delay: 5 diff --git a/roles/etcd_ca/tasks/main.yml b/roles/etcd_ca/tasks/main.yml index c4d5efa14..b4dea4a07 100644 --- a/roles/etcd_ca/tasks/main.yml +++ b/roles/etcd_ca/tasks/main.yml @@ -60,7 +60,8 @@    delegate_to: "{{ etcd_ca_host }}"    run_once: true -- command: > +- name: Create etcd CA certificate +  command: >      openssl req -config {{ etcd_openssl_conf }} -newkey rsa:4096      -keyout {{ etcd_ca_key }} -new -out {{ etcd_ca_cert }}      -x509 -extensions {{ etcd_ca_exts_self }} -batch -nodes diff --git a/roles/etcd_server_certificates/tasks/main.yml b/roles/etcd_server_certificates/tasks/main.yml index 1acdf1c85..242c1e997 100644 --- a/roles/etcd_server_certificates/tasks/main.yml +++ b/roles/etcd_server_certificates/tasks/main.yml @@ -58,6 +58,7 @@                   ~ etcd_cert_prefix ~ 'server.crt' }}"    environment:      SAN: "IP:{{ etcd_ip }}" +  when: etcd_server_certs_missing | bool    delegate_to: "{{ etcd_ca_host }}"  - name: Create the peer csr diff --git a/roles/lib_openshift/library/oc_label.py b/roles/lib_openshift/library/oc_label.py new file mode 100644 index 000000000..9481d2a47 --- /dev/null +++ b/roles/lib_openshift/library/oc_label.py @@ -0,0 +1,1569 @@ +#!/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 json +import os +import re +import shutil +import subprocess +# pylint: disable=import-error +import ruamel.yaml as yaml +from ansible.module_utils.basic import AnsibleModule + +# -*- -*- -*- End included fragment: lib/import.py -*- -*- -*- + +# -*- -*- -*- Begin included fragment: doc/label -*- -*- -*- + +DOCUMENTATION = ''' +--- +module: oc_label +short_description: Create, modify, and idempotently manage openshift labels. +description: +  - Modify openshift labels programmatically. +options: +  state: +    description: +    - State controls the action that will be taken with resource +    - 'present' will create or update and object to the desired state +    - 'absent' will ensure certain labels are removed +    - 'list' will read the labels +    - 'add' will insert labels to the already existing labels +    default: present +    choices: ["present", "absent", "list", "add"] +    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: [] +  kind: +    description: +    - The kind of object that can be managed. +    default: node +    choices: +    - node +    - pod +    - namespace +    aliases: [] +  labels: +    description: +    - A list of labels for the resource. +    - Each list consists of a key and a value. +    - eg, {'key': 'foo', 'value': 'bar'} +    required: false +    default: None +    aliases: [] +  selector: +    description: +    - The selector to apply to the resource query +    required: false +    default: None +    aliases: [] +author: +- "Joel Diaz <jdiaz@redhat.com>" +extends_documentation_fragment: [] +''' + +EXAMPLES = ''' +- name: Add a single label to a node's existing labels +  oc_label: +    name: ip-172-31-5-23.ec2.internal +    state: add +    kind: node +    labels: +      - key: logging-infra-fluentd +        value: 'true' + +- name: remove a label from a node +  oc_label: +    name: ip-172-31-5-23.ec2.internal +    state: absent +    kind: node +    labels: +      - key: color +        value: blue + +- name: Ensure node has these exact labels +  oc_label: +    name: ip-172-31-5-23.ec2.internal +    state: present +    kind: node +    labels: +      - key: color +        value: green +      - key: type +        value: master +      - key: environment +        value: production +''' + +# -*- -*- -*- End included fragment: doc/label -*- -*- -*- + +# -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- +# noqa: E301,E302 + + +class YeditException(Exception): +    ''' Exception class for Yedit ''' +    pass + + +# pylint: disable=too-many-public-methods +class Yedit(object): +    ''' Class to modify yaml files ''' +    re_valid_key = r"(((\[-?\d+\])|([0-9a-zA-Z%s/_-]+)).?)+$" +    re_key = r"(?:\[(-?\d+)\])|([0-9a-zA-Z%s/_-]+)" +    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 yaml_dict ''' +        return self._separator + +    @separator.setter +    def separator(self): +        ''' getter method for yaml_dict ''' +        return self._separator + +    @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 % ''.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 % ''.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, None) +            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): +                    return None + +                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: +                return None + +        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 + +        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, None) +            elif (arr_ind and isinstance(data, list) and +                  int(arr_ind) <= len(data) - 1): +                data = data[int(arr_ind)] +            else: +                return None + +        return data + +    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') + +        tmp_filename = self.filename + '.yedit' +        with open(tmp_filename, 'w') as yfd: +            # pylint: disable=no-member +            if hasattr(self.yaml_dict, 'fa'): +                self.yaml_dict.fa.set_block_style() + +            yfd.write(yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper)) + +        os.rename(tmp_filename, self.filename) + +        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: +                self.yaml_dict = yaml.load(contents, yaml.RoundTripLoader) +                # pylint: disable=no-member +                if hasattr(self.yaml_dict, 'fa'): +                    self.yaml_dict.fa.set_block_style() +            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. %s' % 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): +            # pylint: disable=no-member,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): +            # pylint: disable=no-member,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) + +        # pylint: disable=no-member,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): +            # pylint: disable=no-member,maybe-no-member +            if not isinstance(value, dict): +                raise YeditException('Cannot replace key, value entry in ' + +                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 + +            entry.update(value) +            return (True, self.yaml_dict) + +        elif isinstance(entry, list): +            # pylint: disable=no-member,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 +        tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, +                                                  default_flow_style=False), +                             yaml.RoundTripLoader) +        # pylint: disable=no-member +        if hasattr(self.yaml_dict, 'fa'): +            tmp_copy.fa.set_block_style() +        result = Yedit.add_entry(tmp_copy, path, value, self.separator) +        if not result: +            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 +            tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, default_flow_style=False),  # noqa: E501 +                                 yaml.RoundTripLoader) +            # pylint: disable=no-member +            if hasattr(self.yaml_dict, 'fa'): +                tmp_copy.fa.set_block_style() +            result = Yedit.add_entry(tmp_copy, path, value, self.separator) +            if result: +                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=[%s] vtype=[%s]' +                                     % (inc_value, vtype)) +        elif isinstance(inc_value, bool) and 'str' in vtype: +            inc_value = str(inc_value) + +        # If vtype is not str then go ahead and attempt to yaml load it. +        if isinstance(inc_value, str) and 'str' not in vtype: +            try: +                inc_value = yaml.load(inc_value) +            except Exception: +                raise YeditException('Could not determine type of incoming ' + +                                     'value. value=[%s] vtype=[%s]' +                                     % (type(inc_value), vtype)) + +        return inc_value + +    # pylint: disable=too-many-return-statements,too-many-branches +    @staticmethod +    def run_ansible(module): +        '''perform the idempotent crud operations''' +        yamlfile = Yedit(filename=module.params['src'], +                         backup=module.params['backup'], +                         separator=module.params['separator']) + +        if module.params['src']: +            rval = yamlfile.load() + +            if yamlfile.yaml_dict is None and \ +               module.params['state'] != 'present': +                return {'failed': True, +                        'msg': 'Error opening file [%s].  Verify that the ' + +                               'file exists, that it is has correct' + +                               ' permissions, and is valid yaml.'} + +        if module.params['state'] == 'list': +            if module.params['content']: +                content = Yedit.parse_value(module.params['content'], +                                            module.params['content_type']) +                yamlfile.yaml_dict = content + +            if module.params['key']: +                rval = yamlfile.get(module.params['key']) or {} + +            return {'changed': False, 'result': rval, 'state': "list"} + +        elif module.params['state'] == 'absent': +            if module.params['content']: +                content = Yedit.parse_value(module.params['content'], +                                            module.params['content_type']) +                yamlfile.yaml_dict = content + +            if module.params['update']: +                rval = yamlfile.pop(module.params['key'], +                                    module.params['value']) +            else: +                rval = yamlfile.delete(module.params['key']) + +            if rval[0] and module.params['src']: +                yamlfile.write() + +            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + +        elif module.params['state'] == 'present': +            # check if content is different than what is in the file +            if module.params['content']: +                content = Yedit.parse_value(module.params['content'], +                                            module.params['content_type']) + +                # We had no edits to make and the contents are the same +                if yamlfile.yaml_dict == content and \ +                   module.params['value'] is None: +                    return {'changed': False, +                            'result': yamlfile.yaml_dict, +                            'state': "present"} + +                yamlfile.yaml_dict = content + +            # we were passed a value; parse it +            if module.params['value']: +                value = Yedit.parse_value(module.params['value'], +                                          module.params['value_type']) +                key = module.params['key'] +                if module.params['update']: +                    # pylint: disable=line-too-long +                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 +                                                      module.params['curr_value_format'])  # noqa: E501 + +                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 + +                elif module.params['append']: +                    rval = yamlfile.append(key, value) +                else: +                    rval = yamlfile.put(key, value) + +                if rval[0] and module.params['src']: +                    yamlfile.write() + +                return {'changed': rval[0], +                        'result': rval[1], 'state': "present"} + +            # no edits to make +            if module.params['src']: +                # pylint: disable=redefined-variable-type +                rval = yamlfile.write() +                return {'changed': rval[0], +                        'result': rval[1], +                        'state': "present"} + +        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 + + +# 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 = kubeconfig +        self.all_namespaces = all_namespaces + +    # 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 = '/tmp/%s' % 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''' +        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 = '/tmp/%s' % 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, rname, selector=None): +        '''call oc delete on a resource''' +        cmd = ['delete', resource, rname] +        if selector: +            cmd.append('--selector=%s' % selector) + +        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 = ["%s=%s" % (key, value) 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 = '/tmp/%s' % 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, rname=None, selector=None): +        '''return a resource by name ''' +        cmd = ['get', resource] +        if selector: +            cmd.append('--selector=%s' % selector) +        elif rname: +            cmd.append(rname) + +        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=%s' % selector) + +        cmd.append('--schedulable=%s' % 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=%s' % selector) + +        if pod_selector: +            cmd.append('--pod-selector=%s' % 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=%s' % selector) + +        if dry_run: +            cmd.append('--dry-run') + +        if pod_selector: +            cmd.append('--pod-selector=%s' % pod_selector) + +        if grace_period: +            cmd.append('--grace-period=%s' % 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, stderr + +    # 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 = [] +        if oadm: +            cmds = ['oadm'] +        else: +            cmds = ['oc'] + +        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]) + +        cmds.extend(cmd) + +        rval = {} +        results = '' +        err = None + +        if self.verbose: +            print(' '.join(cmds)) + +        returncode, stdout, stderr = self._run(cmds, input_data) + +        rval = {"returncode": returncode, +                "results": results, +                "cmd": ' '.join(cmds)} + +        if returncode == 0: +            if output: +                if output_type == 'json': +                    try: +                        rval['results'] = json.loads(stdout) +                    except ValueError as err: +                        if "No JSON object could be decoded" in err.args: +                            err = err.args +                elif output_type == 'raw': +                    rval['results'] = stdout + +            if self.verbose: +                print("STDOUT: {0}".format(stdout)) +                print("STDERR: {0}".format(stderr)) + +            if err: +                rval.update({"err": err, +                             "stderr": stderr, +                             "stdout": stdout, +                             "cmd": cmds}) + +        else: +            rval.update({"stderr": stderr, +                         "stdout": stdout, +                         "results": {}}) + +        return rval + + +class Utils(object): +    ''' utilities for openshiftcli modules ''' +    @staticmethod +    def create_file(rname, data, ftype='yaml'): +        ''' create a file in tmp with name and contents''' +        path = os.path.join('/tmp', rname) +        with open(path, 'w') as fds: +            if ftype == 'yaml': +                fds.write(yaml.dump(data, Dumper=yaml.RoundTripDumper)) + +            elif ftype == 'json': +                fds.write(json.dumps(data)) +            else: +                fds.write(data) + +        # Register cleanup when module is done +        atexit.register(Utils.cleanup, [path]) +        return path + +    @staticmethod +    def create_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_file(item['path'], item['data'], ftype=content_type) +            files.append({'name': os.path.basename(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': +            contents = yaml.load(contents, yaml.RoundTripLoader) +        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 yum + +        yum_base = yum.YumBase() +        if yum_base.rpmdb.searchNevra(name='atomic-openshift'): +            return True + +        return False + +    # 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(value) +                            print(user_def[key]) +                        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(api_values) +                        print(user_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): +        '''return all options as a string''' +        return self.stringify() + +    def stringify(self): +        ''' return the options hash as cli params in a string ''' +        rval = [] +        for key, data in self.config_options.items(): +            if data['include'] \ +               and (data['value'] or isinstance(data['value'], int)): +                rval.append('--%s=%s' % (key.replace('_', '-'), data['value'])) + +        return rval + + +# -*- -*- -*- End included fragment: lib/base.py -*- -*- -*- + +# -*- -*- -*- Begin included fragment: class/oc_label.py -*- -*- -*- + + +# pylint: disable=too-many-instance-attributes +class OCLabel(OpenShiftCLI): +    ''' Class to wrap the oc command line tools ''' + +    # pylint allows 5 +    # pylint: disable=too-many-arguments +    def __init__(self, +                 name, +                 namespace, +                 kind, +                 kubeconfig, +                 labels=None, +                 selector=None, +                 verbose=False): +        ''' Constructor for OCLabel ''' +        super(OCLabel, self).__init__(namespace, kubeconfig) +        self.name = name +        self.namespace = namespace +        self.kind = kind +        self.kubeconfig = kubeconfig +        self.labels = labels +        self._curr_labels = None +        self.selector = selector + +    @property +    def current_labels(self): +        '''property for the current labels''' +        if self._curr_labels is None: +            results = self.get() +            self._curr_labels = results['labels'] + +        return self._curr_labels + +    @current_labels.setter +    def current_labels(self, data): +        '''property setter for current labels''' +        self._curr_labels = data + +    def compare_labels(self, host_labels): +        ''' compare incoming labels against current labels''' + +        for label in self.labels: +            if label['key'] not in host_labels or \ +               label['value'] != host_labels[label['key']]: +                return False +        return True + +    def all_user_labels_exist(self): +        ''' return whether all the labels already exist ''' + +        for current_host_labels in self.current_labels: +            rbool = self.compare_labels(current_host_labels) +            if not rbool: +                return False +        return True + +    def any_label_exists(self): +        ''' return whether any single label already exists ''' + +        for current_host_labels in self.current_labels: +            for label in self.labels: +                if label['key'] in current_host_labels: +                    return True +        return False + +    def get_user_keys(self): +        ''' go through list of user key:values and return all keys ''' + +        user_keys = [] +        for label in self.labels: +            user_keys.append(label['key']) + +        return user_keys + +    def get_current_label_keys(self): +        ''' collect all the current label keys ''' + +        current_label_keys = [] +        for current_host_labels in self.current_labels: +            for key in current_host_labels.keys(): +                current_label_keys.append(key) + +        return list(set(current_label_keys)) + +    def get_extra_current_labels(self): +        ''' return list of labels that are currently stored, but aren't +            in user-provided list ''' + +        extra_labels = [] +        user_label_keys = self.get_user_keys() +        current_label_keys = self.get_current_label_keys() + +        for current_key in current_label_keys: +            if current_key not in user_label_keys: +                extra_labels.append(current_key) + +        return extra_labels + +    def extra_current_labels(self): +        ''' return whether there are labels currently stored that user +            hasn't directly provided ''' +        extra_labels = self.get_extra_current_labels() + +        if len(extra_labels) > 0: +            return True + +        return False + +    def replace(self): +        ''' replace currently stored labels with user provided labels ''' +        cmd = self.cmd_template() + +        # First delete any extra labels +        extra_labels = self.get_extra_current_labels() +        if len(extra_labels) > 0: +            for label in extra_labels: +                cmd.append("{}-".format(label)) + +        # Now add/modify the user-provided label list +        if len(self.labels) > 0: +            for label in self.labels: +                cmd.append("{}={}".format(label['key'], label['value'])) + +        # --overwrite for the case where we are updating existing labels +        cmd.append("--overwrite") +        return self.openshift_cmd(cmd) + +    def get(self): +        '''return label information ''' + +        result_dict = {} +        label_list = [] + +        if self.name: +            result = self._get(resource=self.kind, rname=self.name, selector=self.selector) + +            if 'labels' in result['results'][0]['metadata']: +                label_list.append(result['results'][0]['metadata']['labels']) +            else: +                label_list.append({}) + +        else: +            result = self._get(resource=self.kind, selector=self.selector) + +            for item in result['results'][0]['items']: +                if 'labels' in item['metadata']: +                    label_list.append(item['metadata']['labels']) +                else: +                    label_list.append({}) + +        self.current_labels = label_list +        result_dict['labels'] = self.current_labels +        result_dict['item_count'] = len(self.current_labels) +        result['results'] = result_dict + +        return result + +    def cmd_template(self): +        ''' boilerplate oc command for modifying lables on this object ''' +        # let's build the cmd with what we have passed in +        cmd = ["label", self.kind] + +        if self.selector: +            cmd.extend(["--selector", self.selector]) +        elif self.name: +            cmd.extend([self.name]) + +        return cmd + +    def add(self): +        ''' add labels ''' +        cmd = self.cmd_template() + +        for label in self.labels: +            cmd.append("{}={}".format(label['key'], label['value'])) + +        cmd.append("--overwrite") + +        return self.openshift_cmd(cmd) + +    def delete(self): +        '''delete the labels''' +        cmd = self.cmd_template() +        for label in self.labels: +            cmd.append("{}-".format(label['key'])) + +        return self.openshift_cmd(cmd) + +    # pylint: disable=too-many-branches,too-many-return-statements +    @staticmethod +    def run_ansible(params, check_mode=False): +        ''' run the idempotent ansible code + +            prams comes from the ansible portion of this module +            check_mode: does the module support check mode. (module.check_mode) +        ''' +        oc_label = OCLabel(params['name'], +                           params['namespace'], +                           params['kind'], +                           params['kubeconfig'], +                           params['labels'], +                           params['selector'], +                           verbose=params['debug']) + +        state = params['state'] +        name = params['name'] +        selector = params['selector'] + +        api_rval = oc_label.get() + +        ##### +        # Get +        ##### +        if state == 'list': +            return {'changed': False, 'results': api_rval['results'], 'state': "list"} + +        ####### +        # Add +        ####### +        if state == 'add': +            if not (name or selector): +                return {'failed': True, +                        'msg': "Param 'name' or 'selector' is required if state == 'add'"} +            if not oc_label.all_user_labels_exist(): +                if check_mode: +                    return {'changed': False, 'msg': 'Would have performed an addition.'} +                api_rval = oc_label.add() + +                if api_rval['returncode'] != 0: +                    return {'failed': True, 'msg': api_rval} + +                return {'changed': True, 'results': api_rval, 'state': "add"} + +            return {'changed': False, 'state': "add"} + +        ######## +        # Delete +        ######## +        if state == 'absent': +            if not (name or selector): +                return {'failed': True, +                        'msg': "Param 'name' or 'selector' is required if state == 'absent'"} + +            if oc_label.any_label_exists(): +                if check_mode: +                    return {'changed': False, 'msg': 'Would have performed a delete.'} + +                api_rval = oc_label.delete() + +                if api_rval['returncode'] != 0: +                    return {'failed': True, 'msg': api_rval} + +                return {'changed': True, 'results': api_rval, 'state': "absent"} + +            return {'changed': False, 'state': "absent"} + +        if state == 'present': +            ######## +            # Update +            ######## +            if not (name or selector): +                return {'failed': True, +                        'msg': "Param 'name' or 'selector' is required if state == 'present'"} +            # if all the labels passed in don't already exist +            # or if there are currently stored labels that haven't +            # been passed in +            if not oc_label.all_user_labels_exist() or \ +               oc_label.extra_current_labels(): +                if check_mode: +                    return {'changed': False, 'msg': 'Would have made changes.'} + +                api_rval = oc_label.replace() + +                if api_rval['returncode'] != 0: +                    return {'failed': True, 'msg': api_rval} + +                # return the created object +                api_rval = oc_label.get() + +                if api_rval['returncode'] != 0: +                    return {'failed': True, 'msg': api_rval} + +                return {'changed': True, 'results': api_rval, 'state': "present"} + +            return {'changed': False, 'results': api_rval, 'state': "present"} + +        return {'failed': True, +                'changed': False, +                'results': 'Unknown state passed. %s' % state, +                'state': "unknown"} + +# -*- -*- -*- End included fragment: class/oc_label.py -*- -*- -*- + +# -*- -*- -*- Begin included fragment: ansible/oc_label.py -*- -*- -*- + +def main(): +    ''' ansible oc module for labels ''' + +    module = AnsibleModule( +        argument_spec=dict( +            kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), +            state=dict(default='present', type='str', +                       choices=['present', 'absent', 'list', 'add']), +            debug=dict(default=False, type='bool'), +            kind=dict(default='node', type='str', required=True, +                      choices=['node', 'pod', 'namespace']), +            name=dict(default=None, type='str'), +            namespace=dict(default=None, type='str'), +            labels=dict(default=None, type='list'), +            selector=dict(default=None, type='str'), +        ), +        supports_check_mode=True, +        mutually_exclusive=(['name', 'selector']), +    ) + +    results = OCLabel.run_ansible(module.params, module.check_mode) + +    if 'failed' in results: +        module.fail_json(**results) + +    module.exit_json(**results) + +if __name__ == '__main__': +    main() + +# -*- -*- -*- End included fragment: ansible/oc_label.py -*- -*- -*- diff --git a/roles/lib_openshift/src/ansible/oc_label.py b/roles/lib_openshift/src/ansible/oc_label.py new file mode 100644 index 000000000..28f004621 --- /dev/null +++ b/roles/lib_openshift/src/ansible/oc_label.py @@ -0,0 +1,32 @@ +# pylint: skip-file +# flake8: noqa + +def main(): +    ''' ansible oc module for labels ''' + +    module = AnsibleModule( +        argument_spec=dict( +            kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), +            state=dict(default='present', type='str', +                       choices=['present', 'absent', 'list', 'add']), +            debug=dict(default=False, type='bool'), +            kind=dict(default='node', type='str', required=True, +                      choices=['node', 'pod', 'namespace']), +            name=dict(default=None, type='str'), +            namespace=dict(default=None, type='str'), +            labels=dict(default=None, type='list'), +            selector=dict(default=None, type='str'), +        ), +        supports_check_mode=True, +        mutually_exclusive=(['name', 'selector']), +    ) + +    results = OCLabel.run_ansible(module.params, module.check_mode) + +    if 'failed' in results: +        module.fail_json(**results) + +    module.exit_json(**results) + +if __name__ == '__main__': +    main() diff --git a/roles/lib_openshift/src/class/oc_label.py b/roles/lib_openshift/src/class/oc_label.py new file mode 100644 index 000000000..8e1ba9ceb --- /dev/null +++ b/roles/lib_openshift/src/class/oc_label.py @@ -0,0 +1,294 @@ +# pylint: skip-file +# flake8: noqa + + +# pylint: disable=too-many-instance-attributes +class OCLabel(OpenShiftCLI): +    ''' Class to wrap the oc command line tools ''' + +    # pylint allows 5 +    # pylint: disable=too-many-arguments +    def __init__(self, +                 name, +                 namespace, +                 kind, +                 kubeconfig, +                 labels=None, +                 selector=None, +                 verbose=False): +        ''' Constructor for OCLabel ''' +        super(OCLabel, self).__init__(namespace, kubeconfig) +        self.name = name +        self.namespace = namespace +        self.kind = kind +        self.kubeconfig = kubeconfig +        self.labels = labels +        self._curr_labels = None +        self.selector = selector + +    @property +    def current_labels(self): +        '''property for the current labels''' +        if self._curr_labels is None: +            results = self.get() +            self._curr_labels = results['labels'] + +        return self._curr_labels + +    @current_labels.setter +    def current_labels(self, data): +        '''property setter for current labels''' +        self._curr_labels = data + +    def compare_labels(self, host_labels): +        ''' compare incoming labels against current labels''' + +        for label in self.labels: +            if label['key'] not in host_labels or \ +               label['value'] != host_labels[label['key']]: +                return False +        return True + +    def all_user_labels_exist(self): +        ''' return whether all the labels already exist ''' + +        for current_host_labels in self.current_labels: +            rbool = self.compare_labels(current_host_labels) +            if not rbool: +                return False +        return True + +    def any_label_exists(self): +        ''' return whether any single label already exists ''' + +        for current_host_labels in self.current_labels: +            for label in self.labels: +                if label['key'] in current_host_labels: +                    return True +        return False + +    def get_user_keys(self): +        ''' go through list of user key:values and return all keys ''' + +        user_keys = [] +        for label in self.labels: +            user_keys.append(label['key']) + +        return user_keys + +    def get_current_label_keys(self): +        ''' collect all the current label keys ''' + +        current_label_keys = [] +        for current_host_labels in self.current_labels: +            for key in current_host_labels.keys(): +                current_label_keys.append(key) + +        return list(set(current_label_keys)) + +    def get_extra_current_labels(self): +        ''' return list of labels that are currently stored, but aren't +            in user-provided list ''' + +        extra_labels = [] +        user_label_keys = self.get_user_keys() +        current_label_keys = self.get_current_label_keys() + +        for current_key in current_label_keys: +            if current_key not in user_label_keys: +                extra_labels.append(current_key) + +        return extra_labels + +    def extra_current_labels(self): +        ''' return whether there are labels currently stored that user +            hasn't directly provided ''' +        extra_labels = self.get_extra_current_labels() + +        if len(extra_labels) > 0: +            return True + +        return False + +    def replace(self): +        ''' replace currently stored labels with user provided labels ''' +        cmd = self.cmd_template() + +        # First delete any extra labels +        extra_labels = self.get_extra_current_labels() +        if len(extra_labels) > 0: +            for label in extra_labels: +                cmd.append("{}-".format(label)) + +        # Now add/modify the user-provided label list +        if len(self.labels) > 0: +            for label in self.labels: +                cmd.append("{}={}".format(label['key'], label['value'])) + +        # --overwrite for the case where we are updating existing labels +        cmd.append("--overwrite") +        return self.openshift_cmd(cmd) + +    def get(self): +        '''return label information ''' + +        result_dict = {} +        label_list = [] + +        if self.name: +            result = self._get(resource=self.kind, rname=self.name, selector=self.selector) + +            if 'labels' in result['results'][0]['metadata']: +                label_list.append(result['results'][0]['metadata']['labels']) +            else: +                label_list.append({}) + +        else: +            result = self._get(resource=self.kind, selector=self.selector) + +            for item in result['results'][0]['items']: +                if 'labels' in item['metadata']: +                    label_list.append(item['metadata']['labels']) +                else: +                    label_list.append({}) + +        self.current_labels = label_list +        result_dict['labels'] = self.current_labels +        result_dict['item_count'] = len(self.current_labels) +        result['results'] = result_dict + +        return result + +    def cmd_template(self): +        ''' boilerplate oc command for modifying lables on this object ''' +        # let's build the cmd with what we have passed in +        cmd = ["label", self.kind] + +        if self.selector: +            cmd.extend(["--selector", self.selector]) +        elif self.name: +            cmd.extend([self.name]) + +        return cmd + +    def add(self): +        ''' add labels ''' +        cmd = self.cmd_template() + +        for label in self.labels: +            cmd.append("{}={}".format(label['key'], label['value'])) + +        cmd.append("--overwrite") + +        return self.openshift_cmd(cmd) + +    def delete(self): +        '''delete the labels''' +        cmd = self.cmd_template() +        for label in self.labels: +            cmd.append("{}-".format(label['key'])) + +        return self.openshift_cmd(cmd) + +    # pylint: disable=too-many-branches,too-many-return-statements +    @staticmethod +    def run_ansible(params, check_mode=False): +        ''' run the idempotent ansible code + +            prams comes from the ansible portion of this module +            check_mode: does the module support check mode. (module.check_mode) +        ''' +        oc_label = OCLabel(params['name'], +                           params['namespace'], +                           params['kind'], +                           params['kubeconfig'], +                           params['labels'], +                           params['selector'], +                           verbose=params['debug']) + +        state = params['state'] +        name = params['name'] +        selector = params['selector'] + +        api_rval = oc_label.get() + +        ##### +        # Get +        ##### +        if state == 'list': +            return {'changed': False, 'results': api_rval['results'], 'state': "list"} + +        ####### +        # Add +        ####### +        if state == 'add': +            if not (name or selector): +                return {'failed': True, +                        'msg': "Param 'name' or 'selector' is required if state == 'add'"} +            if not oc_label.all_user_labels_exist(): +                if check_mode: +                    return {'changed': False, 'msg': 'Would have performed an addition.'} +                api_rval = oc_label.add() + +                if api_rval['returncode'] != 0: +                    return {'failed': True, 'msg': api_rval} + +                return {'changed': True, 'results': api_rval, 'state': "add"} + +            return {'changed': False, 'state': "add"} + +        ######## +        # Delete +        ######## +        if state == 'absent': +            if not (name or selector): +                return {'failed': True, +                        'msg': "Param 'name' or 'selector' is required if state == 'absent'"} + +            if oc_label.any_label_exists(): +                if check_mode: +                    return {'changed': False, 'msg': 'Would have performed a delete.'} + +                api_rval = oc_label.delete() + +                if api_rval['returncode'] != 0: +                    return {'failed': True, 'msg': api_rval} + +                return {'changed': True, 'results': api_rval, 'state': "absent"} + +            return {'changed': False, 'state': "absent"} + +        if state == 'present': +            ######## +            # Update +            ######## +            if not (name or selector): +                return {'failed': True, +                        'msg': "Param 'name' or 'selector' is required if state == 'present'"} +            # if all the labels passed in don't already exist +            # or if there are currently stored labels that haven't +            # been passed in +            if not oc_label.all_user_labels_exist() or \ +               oc_label.extra_current_labels(): +                if check_mode: +                    return {'changed': False, 'msg': 'Would have made changes.'} + +                api_rval = oc_label.replace() + +                if api_rval['returncode'] != 0: +                    return {'failed': True, 'msg': api_rval} + +                # return the created object +                api_rval = oc_label.get() + +                if api_rval['returncode'] != 0: +                    return {'failed': True, 'msg': api_rval} + +                return {'changed': True, 'results': api_rval, 'state': "present"} + +            return {'changed': False, 'results': api_rval, 'state': "present"} + +        return {'failed': True, +                'changed': False, +                'results': 'Unknown state passed. %s' % state, +                'state': "unknown"} diff --git a/roles/lib_openshift/src/doc/label b/roles/lib_openshift/src/doc/label new file mode 100644 index 000000000..fb3ed2503 --- /dev/null +++ b/roles/lib_openshift/src/doc/label @@ -0,0 +1,92 @@ +# flake8: noqa +# pylint: skip-file + +DOCUMENTATION = ''' +--- +module: oc_label +short_description: Create, modify, and idempotently manage openshift labels. +description: +  - Modify openshift labels programmatically. +options: +  state: +    description: +    - State controls the action that will be taken with resource +    - 'present' will create or update and object to the desired state +    - 'absent' will ensure certain labels are removed +    - 'list' will read the labels +    - 'add' will insert labels to the already existing labels +    default: present +    choices: ["present", "absent", "list", "add"] +    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: [] +  kind: +    description: +    - The kind of object that can be managed. +    default: node +    choices: +    - node +    - pod +    - namespace +    aliases: [] +  labels: +    description: +    - A list of labels for the resource. +    - Each list consists of a key and a value. +    - eg, {'key': 'foo', 'value': 'bar'} +    required: false +    default: None +    aliases: [] +  selector: +    description: +    - The selector to apply to the resource query +    required: false +    default: None +    aliases: [] +author: +- "Joel Diaz <jdiaz@redhat.com>" +extends_documentation_fragment: [] +''' + +EXAMPLES = ''' +- name: Add a single label to a node's existing labels +  oc_label: +    name: ip-172-31-5-23.ec2.internal +    state: add +    kind: node +    labels: +      - key: logging-infra-fluentd +        value: 'true' + +- name: remove a label from a node +  oc_label: +    name: ip-172-31-5-23.ec2.internal +    state: absent +    kind: node +    labels: +      - key: color +        value: blue + +- name: Ensure node has these exact labels +  oc_label: +    name: ip-172-31-5-23.ec2.internal +    state: present +    kind: node +    labels: +      - key: color +        value: green +      - key: type +        value: master +      - key: environment +        value: production +''' diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml index 32c3711d1..6438ff4bb 100644 --- a/roles/lib_openshift/src/sources.yml +++ b/roles/lib_openshift/src/sources.yml @@ -19,6 +19,16 @@ oc_edit.py:  - class/oc_edit.py  - ansible/oc_edit.py +oc_label.py: +- doc/generated +- doc/license +- lib/import.py +- doc/label +- ../../lib_utils/src/class/yedit.py +- lib/base.py +- class/oc_label.py +- ansible/oc_label.py +  oc_obj.py:  - doc/generated  - doc/license diff --git a/roles/lib_openshift/src/test/integration/filter_plugins/filters.py b/roles/lib_openshift/src/test/integration/filter_plugins/filters.py new file mode 100644 index 000000000..6990a11a8 --- /dev/null +++ b/roles/lib_openshift/src/test/integration/filter_plugins/filters.py @@ -0,0 +1,29 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vim: expandtab:tabstop=4:shiftwidth=4 +''' +Custom filters for use in testing +''' + + +class FilterModule(object): +    ''' Custom filters for use in integration testing ''' + +    @staticmethod +    def label_dict_to_key_value_list(label_dict): +        ''' Given a dict of labels/values, return list of key: <key> value: <value> pairs + +            These are only used in integration testing. +        ''' + +        label_list = [] +        for key in label_dict: +            label_list.append({'key': key, 'value': label_dict[key]}) + +        return label_list + +    def filters(self): +        ''' returns a mapping of filters to methods ''' +        return { +            "label_dict_to_key_value_list": self.label_dict_to_key_value_list, +        } diff --git a/roles/lib_openshift/src/test/integration/oc_label.yml b/roles/lib_openshift/src/test/integration/oc_label.yml new file mode 100755 index 000000000..ce9bc25bb --- /dev/null +++ b/roles/lib_openshift/src/test/integration/oc_label.yml @@ -0,0 +1,323 @@ +#!/usr/bin/ansible-playbook --module-path=../../../library/ +# ./oc_label.yml -e "cli_master_test=$OPENSHIFT_MASTER -e "cli_node_test=ip-172-0-31-1.ec2" +--- +- hosts: "{{ cli_master_test }}" +  gather_facts: no +  user: root + +  vars: +  - def_namespace: default +  - def_kind: node + +  pre_tasks: +  - name: ensure needed vars are defined +    fail: +      msg: "{{ item }} not defined" +    when: "{{ item }} is not defined" +    with_items: +    - cli_node_test  # openshift node to be used to add/remove labels to +    - cli_master_test  # ansible inventory instance to run playbook against + +  tasks: +  - name: delete test labels (start from known starting position) +    oc_label: +      state: absent +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabel2 +      - key: testlabel3 + +  - name: list to check whether our test labels already exist +    oc_label: +      state: list +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +    register: original_labels +  - name: assert that testlabel2 and testlabel3 test labels don't exist +    assert: +      that: original_labels['results']['labels'][0]['testlabel2'] is not defined and +            original_labels['results']['labels'][0]['testlabel3'] is not defined +      msg: "{{ original_labels['results']['labels'] }}" + +  - name: add label +    oc_label: +      state: add +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabel2 +        value: "yes" +    register: label_out +  - name: assert adding label marked as changed +    assert: +      that: label_out['changed'] == True +      msg: "{{ label_out }}" + +  - name: test if add label succeeded +    oc_label: +      state: list +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +    register: label_out +  - name: assert that testlabel2 label actually added +    assert: +      that: label_out['results']['labels'][0]['testlabel2'] is defined and +            label_out['results']['labels'][0]['testlabel2'] == "yes" +      msg: "{{ label_out }}" + +  - name: test that re-adding does nothing +    oc_label: +      state: add +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabel2 +        value: "yes" +    register: label_out +  - name: assert that re-adding made no changes +    assert: +      that: label_out['changed'] == False +      msg: "{{ label_out }}" + +  - name: test that modifying existing label marked modified +    oc_label: +      state: add +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabel2 +        value: "different" +    register: label_out +  - name: assert that modifying existing label marked modified +    assert: +      that: label_out['changed'] == True +      msg: "{{ label_out }}" + +  - name: test if modify label actually did modification +    oc_label: +      state: list +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +    register: label_out +  - name: assert that testlabel2 label actually modified +    assert: +      that: label_out['results']['labels'][0]['testlabel2'] is defined and +            label_out['results']['labels'][0]['testlabel2'] == "different" +      msg: "{{ label_out['results']['labels'] }}" + +  - name: delete non-existant label +    oc_label: +      state: absent +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabelnone +    register: label_out +  - name: assert that deleting non-existant label marked not changed +    assert: +      that: label_out['changed'] == False +      msg: "{{ label_out }}" + +  - name: delete label +    oc_label: +      state: absent +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabel2 +    register: label_out +  - name: assert that deleting existing label marked changed +    assert: +      that: label_out['changed'] == True +      msg: "{{ label_out }}" + +  - name: re-delete label +    oc_label: +      state: absent +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabel2 +    register: label_out +  - name: assert that re-deleting label marked not changed +    assert: +      that: label_out['changed'] == False +      msg: "{{ label_out }}" + +  - name: check whether really deleted +    oc_label: +      state: list +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +    register: label_out +  - name: assert label actually deleted +    assert: +      that: label_out['results']['labels'][0]['testlabel2'] is not defined +      msg: "{{ label_out }}" + +  - name: add two labels +    oc_label: +      state: add +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabel2 +        value: "yes" +      - key: testlabel3 +        value: "yes" +    register: label_out +  - name: assert that adding two labels marked as changed +    assert: +      that: label_out['changed'] == True +      msg: "{{ label_out }}" + +  - name: check whether both labels are there +    oc_label: +      state: list +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +    register: label_out +  - name: assert that both labels actually exist +    assert: +      that: label_out['results']['labels'][0]['testlabel2'] is defined and +            label_out['results']['labels'][0]['testlabel2'] == 'yes' and +            label_out['results']['labels'][0]['testlabel3'] is defined and +            label_out['results']['labels'][0]['testlabel3'] == 'yes' +      msg: "{{ label_out['results']['labels'] }}" + +  - name: check whether two deletes work +    oc_label: +      state: absent +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabel2 +      - key: testlabel3 +    register: label_out +  - name: assert that change were made when delete both labels +    assert: +      that: label_out['changed'] == True +      msg: "{{ label_out }}" + +  - name: check whether re-two deletes makes no changes +    oc_label: +      state: absent +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: +      - key: testlabel2 +      - key: testlabel3 +    register: label_out +  - name: assert that change was not made when re-delete both labels +    assert: +      that: label_out['changed'] == False +      msg: "{{ label_out }}" + +  - set_fact: +      original_labels_as_key_value_list: "{{ original_labels['results']['labels'][0] | label_dict_to_key_value_list }}" + +  - name: check that present with original label list makes no changes +    oc_label: +      state: present +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: "{{ original_labels_as_key_value_list }}" +    register: label_out +  - name: assert that no changes are made when current list matches existing list +    assert: +      that: label_out['changed'] == False +      msg: "{{ label_out }}" + +  - name: check that present with extra item makes changes +    oc_label: +      state: present +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: "{{ original_labels_as_key_value_list + [{'key': 'testlabel2', 'value': 'yes'}] }}" +    register: label_out +  - name: assert that changes were made +    assert: +      that: label_out['changed'] == True +      msg: "{{ label_out }}" + +  - name: get current label list +    oc_label: +      state: list +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +    register: label_out +  - name: asssert that new label was actually added +    assert: +      that: label_out['results']['labels'][0]['testlabel2'] is defined and +            label_out['results']['labels'][0]['testlabel2'] == 'yes' +      msg: "{{ label_out['results']['labels'] }}" + +  - name: check that present with changed item makes changes +    oc_label: +      state: present +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: "{{ original_labels_as_key_value_list + [{'key': 'testlabel2', 'value': 'different'}]}}" +    register: label_out +  - name: assert that changes were made when existing key's value is changed +    assert: +      that: label_out['changed'] == True +      msg: "{{ label_out }}" + +  - name: get current label list +    oc_label: +      state: list +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +    register: label_out +  - name: asssert that changed label was actually changed +    assert: +      that: label_out['results']['labels'][0]['testlabel2'] is defined and +            label_out['results']['labels'][0]['testlabel2'] == 'different' +      msg: "{{ label_out['results']['labels'] }}" + +  - name: check that present with removed extra item makes changes +    oc_label: +      state: present +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +      labels: "{{ original_labels_as_key_value_list }}" +    register: label_out +  - name: assert that changes were made +    assert: +      that: label_out['changed'] == True +      msg: "{{ label_out }}" + +  - name: get current label list +    oc_label: +      state: list +      namespace: "{{ def_namespace }}" +      kind: "{{ def_kind }}" +      name: "{{ cli_node_test }}" +    register: label_out +  - name: asssert that present-removed actually removed +    assert: +      that: label_out['results']['labels'][0]['testlabel2'] is not defined +      msg: "{{ label_out }}" diff --git a/roles/lib_openshift/src/test/unit/oc_label.py b/roles/lib_openshift/src/test/unit/oc_label.py new file mode 100755 index 000000000..3f7162070 --- /dev/null +++ b/roles/lib_openshift/src/test/unit/oc_label.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python2 +''' + Unit tests for oc label +''' +# To run +# python -m unittest version +# +# . +# Ran 1 test in 0.597s +# +# OK + +import os +import sys +import unittest +import mock + +# Removing invalid variable names for tests so that I can +# keep them brief +# pylint: disable=invalid-name,no-name-in-module +# Disable import-error b/c our libraries aren't loaded in jenkins +# pylint: disable=import-error +# place class in our python path +module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501 +sys.path.insert(0, module_path) +from oc_label import OCLabel  # noqa: E402 + + +class OCLabelTest(unittest.TestCase): +    ''' +     Test class for OCLabel +    ''' + +    def setUp(self): +        ''' setup method will create a file and set to known configuration ''' +        pass + +    @mock.patch('oc_label.OCLabel._run') +    def test_state_list(self, mock_cmd): +        ''' Testing a label list ''' +        params = {'name': 'default', +                  'namespace': 'default', +                  'labels': None, +                  'state': 'list', +                  'kind': 'namespace', +                  'selector': None, +                  'kubeconfig': '/etc/origin/master/admin.kubeconfig', +                  'debug': False} + +        ns = '''{ +            "kind": "Namespace", +            "apiVersion": "v1", +            "metadata": { +                "name": "default", +                "selfLink": "/api/v1/namespaces/default", +                "uid": "c45b9547-e3d3-11e6-ba9c-0eece8f2ce22", +                "resourceVersion": "403024", +                "creationTimestamp": "2017-01-26T14:28:55Z", +                "labels": { +                    "storage_pv_quota": "False" +                }, +                "annotations": { +                    "openshift.io/node-selector": "", +                    "openshift.io/sa.initialized-roles": "true", +                    "openshift.io/sa.scc.mcs": "s0:c1,c0", +                    "openshift.io/sa.scc.supplemental-groups": "1000000000/10000", +                    "openshift.io/sa.scc.uid-range": "1000000000/10000" +                } +            }, +            "spec": { +                "finalizers": [ +                    "kubernetes", +                    "openshift.io/origin" +                ] +            }, +            "status": { +                "phase": "Active" +            } +        }''' + +        mock_cmd.side_effect = [ +            (0, ns, ''), +        ] + +        results = OCLabel.run_ansible(params, False) + +        self.assertFalse(results['changed']) +        self.assertTrue(results['results']['labels'] == [{'storage_pv_quota': 'False'}]) + +    @mock.patch('oc_label.OCLabel._run') +    def test_state_present(self, mock_cmd): +        ''' Testing a label list ''' +        params = {'name': 'default', +                  'namespace': 'default', +                  'labels': [ +                      {'key': 'awesomens', 'value': 'testinglabel'}, +                      {'key': 'storage_pv_quota', 'value': 'False'} +                  ], +                  'state': 'present', +                  'kind': 'namespace', +                  'selector': None, +                  'kubeconfig': '/etc/origin/master/admin.kubeconfig', +                  'debug': False} + +        ns = '''{ +            "kind": "Namespace", +            "apiVersion": "v1", +            "metadata": { +                "name": "default", +                "selfLink": "/api/v1/namespaces/default", +                "uid": "c45b9547-e3d3-11e6-ba9c-0eece8f2ce22", +                "resourceVersion": "403024", +                "creationTimestamp": "2017-01-26T14:28:55Z", +                "labels": { +                    "storage_pv_quota": "False" +                }, +                "annotations": { +                    "openshift.io/node-selector": "", +                    "openshift.io/sa.initialized-roles": "true", +                    "openshift.io/sa.scc.mcs": "s0:c1,c0", +                    "openshift.io/sa.scc.supplemental-groups": "1000000000/10000", +                    "openshift.io/sa.scc.uid-range": "1000000000/10000" +                } +            }, +            "spec": { +                "finalizers": [ +                    "kubernetes", +                    "openshift.io/origin" +                ] +            }, +            "status": { +                "phase": "Active" +            } +        }''' + +        ns1 = '''{ +            "kind": "Namespace", +            "apiVersion": "v1", +            "metadata": { +                "name": "default", +                "selfLink": "/api/v1/namespaces/default", +                "uid": "c45b9547-e3d3-11e6-ba9c-0eece8f2ce22", +                "resourceVersion": "403024", +                "creationTimestamp": "2017-01-26T14:28:55Z", +                "labels": { +                    "storage_pv_quota": "False", +                    "awesomens": "testinglabel" +                }, +                "annotations": { +                    "openshift.io/node-selector": "", +                    "openshift.io/sa.initialized-roles": "true", +                    "openshift.io/sa.scc.mcs": "s0:c1,c0", +                    "openshift.io/sa.scc.supplemental-groups": "1000000000/10000", +                    "openshift.io/sa.scc.uid-range": "1000000000/10000" +                } +            }, +            "spec": { +                "finalizers": [ +                    "kubernetes", +                    "openshift.io/origin" +                ] +            }, +            "status": { +                "phase": "Active" +            } +        }''' + +        mock_cmd.side_effect = [ +            (0, ns, ''), +            (0, '', ''), +            (0, ns1, ''), +        ] + +        results = OCLabel.run_ansible(params, False) + +        self.assertTrue(results['changed']) +        self.assertTrue(results['results']['results']['labels'][0] == +                        {'storage_pv_quota': 'False', 'awesomens': 'testinglabel'}) + +    def tearDown(self): +        '''TearDown method''' +        pass + + +if __name__ == "__main__": +    unittest.main() diff --git a/roles/lib_openshift/tasks/main.yml b/roles/lib_openshift/tasks/main.yml new file mode 100644 index 000000000..2980c8a8d --- /dev/null +++ b/roles/lib_openshift/tasks/main.yml @@ -0,0 +1,5 @@ +--- +- name: lib_openshift ensure python-ruamel-yaml package is on target +  package: +    name: python-ruamel-yaml +    state: present diff --git a/roles/lib_utils/tasks/main.yml b/roles/lib_utils/tasks/main.yml new file mode 100644 index 000000000..8a350da88 --- /dev/null +++ b/roles/lib_utils/tasks/main.yml @@ -0,0 +1,5 @@ +--- +- name: lib_utils ensure python-ruamel-yaml package is on target +  package: +    name: python-ruamel-yaml +    state: present diff --git a/roles/openshift_ca/tasks/main.yml b/roles/openshift_ca/tasks/main.yml index e21397170..4efc77f11 100644 --- a/roles/openshift_ca/tasks/main.yml +++ b/roles/openshift_ca/tasks/main.yml @@ -41,10 +41,9 @@    run_once: true  - set_fact: -    master_ca_missing: "{{ true if openshift_certificates_redeploy | default(false) | bool -                           else False in (g_master_ca_stat_result.results -                                         | oo_collect(attribute='stat.exists') -                                         | list) }}" +    master_ca_missing: "{{ False in (g_master_ca_stat_result.results +                                     | oo_collect(attribute='stat.exists') +                                     | list) }}"    run_once: true  - name: Retain original serviceaccount keys @@ -61,7 +60,6 @@    copy:      src: "{{ item.src }}"      dest: "{{ openshift_ca_config_dir }}/{{ item.dest }}" -    force: "{{ true if openshift_certificates_redeploy_ca | default(false) | bool else false }}"    with_items:    - src: "{{ (openshift_master_ca_certificate | default({'certfile':none})).certfile }}"      dest: ca.crt @@ -73,25 +71,35 @@  - name: Create ca serial    copy: -    content: "1" +    content: "00"      dest: "{{ openshift_ca_config_dir }}/ca.serial.txt" -    force: "{{ true if openshift_certificates_redeploy | default(false) | bool else false }}" +    force: "{{ openshift_certificates_redeploy | default(false) | bool }}"    when: openshift_master_ca_certificate is defined    delegate_to: "{{ openshift_ca_host }}"    run_once: true +- find: +    paths: "{{ openshift.common.config_base }}/master/legacy-ca/" +    patterns: ".*-ca.crt" +    use_regex: true +  register: g_master_legacy_ca_result + +# This should NOT replace the CA due to --overwrite=false when a CA already exists.  - name: Create the master certificates if they do not already exist    command: >      {{ openshift.common.client_binary }} adm create-master-certs      {% for named_ca_certificate in openshift.master.named_certificates | default([]) | oo_collect('cafile') %}      --certificate-authority {{ named_ca_certificate }}      {% endfor %} +    {% for legacy_ca_certificate in g_master_legacy_ca_result.files | default([]) | oo_collect('path') %} +    --certificate-authority {{ legacy_ca_certificate }} +    {% endfor %}      --hostnames={{ openshift.common.all_hostnames | join(',') }}      --master={{ openshift.master.api_url }}      --public-master={{ openshift.master.public_api_url }}      --cert-dir={{ openshift_ca_config_dir }}      --overwrite=false -  when: master_ca_missing | bool +  when: master_ca_missing | bool or openshift_certificates_redeploy | default(false) | bool    delegate_to: "{{ openshift_ca_host }}"    run_once: true diff --git a/roles/openshift_examples/files/examples/v1.4/db-templates/mongodb-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.4/db-templates/mongodb-ephemeral-template.json index 8b8fcb58b..c38d2680b 100644 --- a/roles/openshift_examples/files/examples/v1.4/db-templates/mongodb-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.4/db-templates/mongodb-ephemeral-template.json @@ -196,7 +196,8 @@        "name": "MEMORY_LIMIT",        "displayName": "Memory Limit",        "description": "Maximum amount of memory the container can use.", -      "value": "512Mi" +      "value": "512Mi", +      "required": true      },      {        "name": "NAMESPACE", diff --git a/roles/openshift_examples/files/examples/v1.4/db-templates/mongodb-persistent-template.json b/roles/openshift_examples/files/examples/v1.4/db-templates/mongodb-persistent-template.json index 72d3a8556..e8853d8ff 100644 --- a/roles/openshift_examples/files/examples/v1.4/db-templates/mongodb-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.4/db-templates/mongodb-persistent-template.json @@ -213,7 +213,8 @@        "name": "MEMORY_LIMIT",        "displayName": "Memory Limit",        "description": "Maximum amount of memory the container can use.", -      "value": "512Mi" +      "value": "512Mi", +      "required": true      },      {        "name": "NAMESPACE", diff --git a/roles/openshift_examples/files/examples/v1.4/db-templates/mysql-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.4/db-templates/mysql-ephemeral-template.json index 34dd2ed78..f7bcfe2ed 100644 --- a/roles/openshift_examples/files/examples/v1.4/db-templates/mysql-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.4/db-templates/mysql-ephemeral-template.json @@ -196,7 +196,8 @@        "name": "MEMORY_LIMIT",        "displayName": "Memory Limit",        "description": "Maximum amount of memory the container can use.", -      "value": "512Mi" +      "value": "512Mi", +      "required": true      },      {        "name": "NAMESPACE", diff --git a/roles/openshift_examples/files/examples/v1.4/db-templates/postgresql-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.4/db-templates/postgresql-ephemeral-template.json index 0d0a2a629..64d5e2b32 100644 --- a/roles/openshift_examples/files/examples/v1.4/db-templates/postgresql-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.4/db-templates/postgresql-ephemeral-template.json @@ -11,7 +11,7 @@        "tags": "database,postgresql"      }    }, -  "message": "The following service(s) have been created in your project: ${DATABASE_SERVICE_NAME}.\n\n       Username: ${POSTGRESQL_USER}\n       Password: ${POSTGRESQL_PASSWORD}\n  Database Name: ${POSTGRESQL_DATABASE}\n Connection URL: mysql://${DATABASE_SERVICE_NAME}:5432/\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/blob/master/9.5.", +  "message": "The following service(s) have been created in your project: ${DATABASE_SERVICE_NAME}.\n\n       Username: ${POSTGRESQL_USER}\n       Password: ${POSTGRESQL_PASSWORD}\n  Database Name: ${POSTGRESQL_DATABASE}\n Connection URL: postgresql://${DATABASE_SERVICE_NAME}:5432/\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/blob/master/9.5.",    "labels": {      "template": "postgresql-ephemeral-template"    }, @@ -186,7 +186,8 @@        "name": "MEMORY_LIMIT",        "displayName": "Memory Limit",        "description": "Maximum amount of memory the container can use.", -      "value": "512Mi" +      "value": "512Mi", +      "required": true      },      {        "name": "NAMESPACE", diff --git a/roles/openshift_examples/files/examples/v1.4/db-templates/postgresql-persistent-template.json b/roles/openshift_examples/files/examples/v1.4/db-templates/postgresql-persistent-template.json index 257726cfd..6c101f9d2 100644 --- a/roles/openshift_examples/files/examples/v1.4/db-templates/postgresql-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.4/db-templates/postgresql-persistent-template.json @@ -11,7 +11,7 @@        "tags": "database,postgresql"      }    }, -  "message": "The following service(s) have been created in your project: ${DATABASE_SERVICE_NAME}.\n\n       Username: ${POSTGRESQL_USER}\n       Password: ${POSTGRESQL_PASSWORD}\n  Database Name: ${POSTGRESQL_DATABASE}\n Connection URL: mysql://${DATABASE_SERVICE_NAME}:5432/\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/blob/master/9.5.", +  "message": "The following service(s) have been created in your project: ${DATABASE_SERVICE_NAME}.\n\n       Username: ${POSTGRESQL_USER}\n       Password: ${POSTGRESQL_PASSWORD}\n  Database Name: ${POSTGRESQL_DATABASE}\n Connection URL: postgresql://${DATABASE_SERVICE_NAME}:5432/\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/blob/master/9.5.",    "labels": {      "template": "postgresql-persistent-template"    }, @@ -203,7 +203,8 @@        "name": "MEMORY_LIMIT",        "displayName": "Memory Limit",        "description": "Maximum amount of memory the container can use.", -      "value": "512Mi" +      "value": "512Mi", +      "required": true      },      {        "name": "NAMESPACE", diff --git a/roles/openshift_examples/files/examples/v1.4/db-templates/redis-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.4/db-templates/redis-ephemeral-template.json index c9ae8a539..b97e1fd29 100644 --- a/roles/openshift_examples/files/examples/v1.4/db-templates/redis-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.4/db-templates/redis-ephemeral-template.json @@ -157,7 +157,8 @@        "name": "MEMORY_LIMIT",        "displayName": "Memory Limit",        "description": "Maximum amount of memory the container can use.", -      "value": "512Mi" +      "value": "512Mi", +      "required": true      },      {        "name": "NAMESPACE", diff --git a/roles/openshift_examples/files/examples/v1.4/db-templates/redis-persistent-template.json b/roles/openshift_examples/files/examples/v1.4/db-templates/redis-persistent-template.json index e9db9ec9d..dc167da41 100644 --- a/roles/openshift_examples/files/examples/v1.4/db-templates/redis-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.4/db-templates/redis-persistent-template.json @@ -174,7 +174,8 @@        "name": "MEMORY_LIMIT",        "displayName": "Memory Limit",        "description": "Maximum amount of memory the container can use.", -      "value": "512Mi" +      "value": "512Mi", +      "required": true      },      {        "name": "NAMESPACE", diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/README.md b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/README.md index 62765e03d..f48d8d4a8 100644 --- a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/README.md +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/README.md @@ -12,10 +12,15 @@ reference and supply your forked repository as the source-repository when  instantiating them.  * [CakePHP](https://raw.githubusercontent.com/openshift/cakephp-ex/master/openshift/templates/cakephp-mysql.json) - Provides a basic CakePHP application with a MySQL database. For more information see the [source repository](https://github.com/openshift/cakephp-ex). +* [CakePHP persistent](https://raw.githubusercontent.com/openshift/cakephp-ex/master/openshift/templates/cakephp-mysql-persistent.json) - Provides a basic CakePHP application with a persistent MySQL database. Note: requires available persistent volumes.  For more information see the [source repository](https://github.com/openshift/cakephp-ex).  * [Dancer](https://raw.githubusercontent.com/openshift/dancer-ex/master/openshift/templates/dancer-mysql.json) - Provides a basic Dancer (Perl) application with a MySQL database. For more information see the [source repository](https://github.com/openshift/dancer-ex). +* [Dancer persistent](https://raw.githubusercontent.com/openshift/dancer-ex/master/openshift/templates/dancer-mysql-persistent.json) - Provides a basic Dancer (Perl) application with a persistent MySQL database. Note: requires available persistent volumes.  For more information see the [source repository](https://github.com/openshift/dancer-ex).  * [Django](https://raw.githubusercontent.com/openshift/django-ex/master/openshift/templates/django-postgresql.json) - Provides a basic Django (Python) application with a PostgreSQL database. For more information see the [source repository](https://github.com/openshift/django-ex). +* [Django persistent](https://raw.githubusercontent.com/openshift/django-ex/master/openshift/templates/django-postgresql-persistent.json) - Provides a basic Django (Python) application with a persistent PostgreSQL database. Note: requires available persistent volumes.  For more information see the [source repository](https://github.com/openshift/django-ex).  * [NodeJS](https://raw.githubusercontent.com/openshift/nodejs-ex/master/openshift/templates/nodejs-mongodb.json) - Provides a basic NodeJS application with a MongoDB database. For more information see the [source repository](https://github.com/openshift/nodejs-ex). +* [NodeJS persistent](https://raw.githubusercontent.com/openshift/nodejs-ex/master/openshift/templates/nodejs-mongodb-persistent.json) - Provides a basic NodeJS application with a persistent MongoDB database. Note: requires available persistent volumes.  For more information see the [source repository](https://github.com/openshift/nodejs-ex).  * [Rails](https://raw.githubusercontent.com/openshift/rails-ex/master/openshift/templates/rails-postgresql.json) - Provides a basic Rails (Ruby) application with a PostgreSQL database. For more information see the [source repository](https://github.com/openshift/rails-ex). +* [Rails persistent](https://raw.githubusercontent.com/openshift/rails-ex/master/openshift/templates/rails-postgresql-persistent.json) - Provides a basic Rails (Ruby) application with a persistent PostgreSQL database. Note: requires available persistent volumes.  For more information see the [source repository](https://github.com/openshift/rails-ex).  Note: This file is processed by `hack/update-external-examples.sh`. New examples  must follow the exact syntax of the existing entries. Files in this directory diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/cakephp-mysql-persistent.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/cakephp-mysql-persistent.json new file mode 100644 index 000000000..0ba57864e --- /dev/null +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/cakephp-mysql-persistent.json @@ -0,0 +1,575 @@ +{ +  "kind": "Template", +  "apiVersion": "v1", +  "metadata": { +    "name": "cakephp-mysql-persistent", +    "annotations": { +      "openshift.io/display-name": "CakePHP + MySQL (Persistent)", +      "description": "An example CakePHP application with a MySQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/cakephp-ex/blob/master/README.md.", +      "tags": "quickstart,php,cakephp", +      "iconClass": "icon-php" +    } +  }, +  "message": "The following service(s) have been created in your project: ${NAME}, ${DATABASE_SERVICE_NAME}.\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/openshift/cake-ex/blob/master/README.md.", +  "labels": { +    "template": "cakephp-mysql-persistent" +  }, +  "objects": [ +    { +      "kind": "Secret", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "stringData" : { +        "database-user" : "${DATABASE_USER}", +        "database-password" : "${DATABASE_PASSWORD}", +        "cakephp-secret-token" : "${CAKEPHP_SECRET_TOKEN}", +        "cakephp-security-salt" : "${CAKEPHP_SECURITY_SALT}", +        "cakephp-security-cipher-seed" : "${CAKEPHP_SECURITY_CIPHER_SEED}" +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Exposes and load balances the application pods", +          "service.alpha.openshift.io/dependencies": "[{\"name\": \"${DATABASE_SERVICE_NAME}\", \"kind\": \"Service\"}]" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "web", +            "port": 8080, +            "targetPort": 8080 +          } +        ], +        "selector": { +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "Route", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "spec": { +        "host": "${APPLICATION_DOMAIN}", +        "to": { +          "kind": "Service", +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "ImageStream", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Keeps track of changes in the application image" +        } +      } +    }, +    { +      "kind": "BuildConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to build the application" +        } +      }, +      "spec": { +        "source": { +          "type": "Git", +          "git": { +            "uri": "${SOURCE_REPOSITORY_URL}", +            "ref": "${SOURCE_REPOSITORY_REF}" +          }, +          "contextDir": "${CONTEXT_DIR}" +        }, +        "strategy": { +          "type": "Source", +          "sourceStrategy": { +            "from": { +              "kind": "ImageStreamTag", +              "namespace": "${NAMESPACE}", +              "name": "php:7.0" +            }, +            "env":  [ +              { +                "name": "COMPOSER_MIRROR", +                "value": "${COMPOSER_MIRROR}" +              } +            ] +          } +        }, +        "output": { +          "to": { +            "kind": "ImageStreamTag", +            "name": "${NAME}:latest" +          } +        }, +        "triggers": [ +          { +            "type": "ImageChange" +          }, +          { +            "type": "ConfigChange" +          }, +          { +            "type": "GitHub", +            "github": { +              "secret": "${GITHUB_WEBHOOK_SECRET}" +            } +          } +        ] +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to deploy the application server" +        } +      }, +      "spec": { +        "strategy": { +          "type": "Recreate", +          "recreateParams": { +            "pre": { +              "failurePolicy": "Retry", +              "execNewPod": { +                "command": [ +                  "./migrate-database.sh" +                ], +                "containerName": "cakephp-mysql-persistent" +              } +            } +          } +        }, +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "cakephp-mysql-persistent" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "name": "${NAME}:latest" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${NAME}", +            "labels": { +              "name": "${NAME}" +            } +          }, +          "spec": { +            "containers": [ +              { +                "name": "cakephp-mysql-persistent", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 8080 +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 3, +                  "httpGet": { +                    "path": "/health.php", +                    "port": 8080 +                  } +                }, +                "livenessProbe": { +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 30, +                  "httpGet": { +                    "path": "/", +                    "port": 8080 +                  } +                }, +                "env": [ +                  { +                    "name": "DATABASE_SERVICE_NAME", +                    "value": "${DATABASE_SERVICE_NAME}" +                  }, +                  { +                    "name": "DATABASE_ENGINE", +                    "value": "${DATABASE_ENGINE}" +                  }, +                  { +                    "name": "DATABASE_NAME", +                    "value": "${DATABASE_NAME}" +                  }, +                  { +                    "name": "DATABASE_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "DATABASE_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                    "name": "CAKEPHP_SECRET_TOKEN", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "cakephp-secret-token" +                      } +                    } +                  }, +                  { +                    "name": "CAKEPHP_SECURITY_SALT", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "cakephp-security-salt" +                      } +                    } +                  }, +                  { +                    "name": "CAKEPHP_SECURITY_CIPHER_SEED", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "cakephp-security-cipher-seed" +                      } +                    } +                  }, +                  { +                    "name": "OPCACHE_REVALIDATE_FREQ", +                    "value": "${OPCACHE_REVALIDATE_FREQ}" +                  } +                ], +                "resources": { +                  "limits": { +                    "memory": "${MEMORY_LIMIT}" +                  } +                } +              } +            ] +          } +        } +      } +    }, +    { +      "kind": "PersistentVolumeClaim", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}" +      }, +      "spec": { +        "accessModes": [ +          "ReadWriteOnce" +        ], +        "resources": { +          "requests": { +            "storage": "${VOLUME_CAPACITY}" +          } +        } +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Exposes the database server" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "mysql", +            "port": 3306, +            "targetPort": 3306 +          } +        ], +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        } +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Defines how to deploy the database" +        } +      }, +      "spec": { +        "strategy": { +          "type": "Recreate" +        }, +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "mysql" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "namespace": "${NAMESPACE}", +                "name": "mysql:5.7" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${DATABASE_SERVICE_NAME}", +            "labels": { +              "name": "${DATABASE_SERVICE_NAME}" +            } +          }, +          "spec": { +            "volumes": [ +              { +                "name": "${DATABASE_SERVICE_NAME}-data", +                "persistentVolumeClaim": { +                  "claimName": "${DATABASE_SERVICE_NAME}" +                } +              } +            ], +            "containers": [ +              { +                "name": "mysql", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 3306 +                  } +                ], +                "volumeMounts": [ +                  { +                    "name": "${DATABASE_SERVICE_NAME}-data", +                    "mountPath": "/var/lib/mysql/data" +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 5, +                  "exec": { +                    "command": [ "/bin/sh", "-i", "-c", "MYSQL_PWD='${DATABASE_PASSWORD}' mysql -h 127.0.0.1 -u ${DATABASE_USER} -D ${DATABASE_NAME} -e 'SELECT 1'" ] +                  } +                }, +                "livenessProbe": { +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 30, +                  "tcpSocket": { +                    "port": 3306 +                  } +                }, +                "env": [ +                  { +                    "name": "MYSQL_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "MYSQL_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                    "name": "MYSQL_DATABASE", +                    "value": "${DATABASE_NAME}" +                  } +                ], +                "resources": { +                  "limits": { +                    "memory": "${MEMORY_MYSQL_LIMIT}" +                  } +                } +              } +            ] +          } +        } +      } +    } +  ], +  "parameters": [ +    { +      "name": "NAME", +      "displayName": "Name", +      "description": "The name assigned to all of the frontend objects defined in this template.", +      "required": true, +      "value": "cakephp-mysql-persistent" +    }, +    { +      "name": "NAMESPACE", +      "displayName": "Namespace", +      "description": "The OpenShift Namespace where the ImageStream resides.", +      "required": true, +      "value": "openshift" +    }, +    { +      "name": "MEMORY_LIMIT", +      "displayName": "Memory Limit", +      "description": "Maximum amount of memory the CakePHP container can use.", +      "required": true, +      "value": "512Mi" +    }, +    { +      "name": "MEMORY_MYSQL_LIMIT", +      "displayName": "Memory Limit (MySQL)", +      "description": "Maximum amount of memory the MySQL container can use.", +      "required": true, +      "value": "512Mi" +    }, +    { +      "name": "VOLUME_CAPACITY", +      "displayName": "Volume Capacity", +      "description": "Volume space available for data, e.g. 512Mi, 2Gi", +      "value": "1Gi", +      "required": true +    }, +    { +      "name": "SOURCE_REPOSITORY_URL", +      "displayName": "Git Repository URL", +      "description": "The URL of the repository with your application source code.", +      "required": true, +      "value": "https://github.com/openshift/cakephp-ex.git" +    }, +    { +      "name": "SOURCE_REPOSITORY_REF", +      "displayName": "Git Reference", +      "description": "Set this to a branch name, tag or other ref of your repository if you are not using the default branch." +    }, +    { +      "name": "CONTEXT_DIR", +      "displayName": "Context Directory", +      "description": "Set this to the relative path to your project if it is not in the root of your repository." +    }, +    { +      "name": "APPLICATION_DOMAIN", +      "displayName": "Application Hostname", +      "description": "The exposed hostname that will route to the CakePHP service, if left blank a value will be defaulted.", +      "value": "" +    }, +    { +      "name": "GITHUB_WEBHOOK_SECRET", +      "displayName": "GitHub Webhook Secret", +      "description": "A secret string used to configure the GitHub webhook.", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{40}" +    }, +    { +      "name": "DATABASE_SERVICE_NAME", +      "displayName": "Database Service Name", +      "required": true, +      "value": "mysql" +    }, +    { +      "name": "DATABASE_ENGINE", +      "displayName": "Database Engine", +      "description": "Database engine: postgresql, mysql or sqlite (default).", +      "required": true, +      "value": "mysql" +    }, +    { +      "name": "DATABASE_NAME", +      "displayName": "Database Name", +      "required": true, +      "value": "default" +    }, +    { +      "name": "DATABASE_USER", +      "displayName": "Database User", +      "required": true, +      "value": "cakephp" +    }, +    { +      "name": "DATABASE_PASSWORD", +      "displayName": "Database Password", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{16}" +    }, +    { +      "name": "CAKEPHP_SECRET_TOKEN", +      "displayName": "CakePHP secret token", +      "description": "Set this to a long random string.", +      "generate": "expression", +      "from": "[\\w]{50}" +    }, +    { +      "name": "CAKEPHP_SECURITY_SALT", +      "displayName": "CakePHP Security Salt", +      "description": "Security salt for session hash.", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{40}" +    }, +    { +      "name": "CAKEPHP_SECURITY_CIPHER_SEED", +      "displayName": "CakePHP Security Cipher Seed", +      "description": "Security cipher seed for session hash.", +      "generate": "expression", +      "from": "[0-9]{30}" +    }, +    { +      "name": "OPCACHE_REVALIDATE_FREQ", +      "displayName": "OPcache Revalidation Frequency", +      "description": "How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.", +      "value": "2" +    }, +    { +      "name": "COMPOSER_MIRROR", +      "displayName": "Custom Composer Mirror URL", +      "description": "The custom Composer mirror URL", +      "value": "" +    } +  ] +} diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/cakephp-mysql.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/cakephp-mysql.json index 9dbbf89d1..9732e59e1 100644 --- a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/cakephp-mysql.json +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/cakephp-mysql.json @@ -22,8 +22,11 @@          "name": "${NAME}"        },        "stringData" : { -        "databaseUser" : "${DATABASE_USER}", -        "databasePassword" : "${DATABASE_PASSWORD}" +        "database-user" : "${DATABASE_USER}", +        "database-password" : "${DATABASE_PASSWORD}", +        "cakephp-secret-token" : "${CAKEPHP_SECRET_TOKEN}", +        "cakephp-security-salt" : "${CAKEPHP_SECURITY_SALT}", +        "cakephp-security-cipher-seed" : "${CAKEPHP_SECURITY_CIPHER_SEED}"        }      },      { @@ -97,12 +100,12 @@              "from": {                "kind": "ImageStreamTag",                "namespace": "${NAMESPACE}", -              "name": "php:5.6" +              "name": "php:7.0"              },              "env":  [                { -                  "name": "COMPOSER_MIRROR", -                  "value": "${COMPOSER_MIRROR}" +                "name": "COMPOSER_MIRROR", +                "value": "${COMPOSER_MIRROR}"                }              ]            } @@ -201,12 +204,12 @@                    }                  },                  "livenessProbe": { -                    "timeoutSeconds": 3, -                    "initialDelaySeconds": 30, -                    "httpGet": { -                        "path": "/", -                        "port": 8080 -                    } +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 30, +                  "httpGet": { +                    "path": "/", +                    "port": 8080 +                  }                  },                  "env": [                    { @@ -226,7 +229,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseUser" +                        "key" : "database-user"                        }                      }                    }, @@ -235,21 +238,36 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databasePassword" +                        "key" : "database-password"                        }                      }                    },                    {                      "name": "CAKEPHP_SECRET_TOKEN", -                    "value": "${CAKEPHP_SECRET_TOKEN}" +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "cakephp-secret-token" +                      } +                    }                    },                    {                      "name": "CAKEPHP_SECURITY_SALT", -                    "value": "${CAKEPHP_SECURITY_SALT}" +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "cakephp-security-salt" +                      } +                    }                    },                    {                      "name": "CAKEPHP_SECURITY_CIPHER_SEED", -                    "value": "${CAKEPHP_SECURITY_CIPHER_SEED}" +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "cakephp-security-cipher-seed" +                      } +                    }                    },                    {                      "name": "OPCACHE_REVALIDATE_FREQ", @@ -257,9 +275,9 @@                    }                  ],                  "resources": { -		    "limits": { -			"memory": "${MEMORY_LIMIT}" -		    } +                  "limits": { +                    "memory": "${MEMORY_LIMIT}" +                  }                  }                }              ] @@ -313,7 +331,7 @@                "from": {                  "kind": "ImageStreamTag",                  "namespace": "${NAMESPACE}", -                "name": "mysql:5.6" +                "name": "mysql:5.7"                }              }            }, @@ -362,40 +380,40 @@                    }                  },                  "livenessProbe": { -                    "timeoutSeconds": 1, -                    "initialDelaySeconds": 30, -                    "tcpSocket": { -                        "port": 3306 -                    } +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 30, +                  "tcpSocket": { +                    "port": 3306 +                  }                  },                  "env": [ -                    { -                      "name": "MYSQL_USER", -                      "valueFrom": { -                        "secretKeyRef" : { -                          "name" : "${NAME}", -                          "key" : "databaseUser" -                        } +                  { +                    "name": "MYSQL_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "MYSQL_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password"                        } -                    }, -                    { -                      "name": "MYSQL_PASSWORD", -                      "valueFrom": { -                        "secretKeyRef" : { -                          "name" : "${NAME}", -                          "key" : "databasePassword" -                          } -                        } -                    }, -                    { -                        "name": "MYSQL_DATABASE", -                        "value": "${DATABASE_NAME}"                      } +                  }, +                  { +                    "name": "MYSQL_DATABASE", +                    "value": "${DATABASE_NAME}" +                  }                  ],                  "resources": { -                    "limits": { -                        "memory": "${MEMORY_MYSQL_LIMIT}" -                    } +                  "limits": { +                    "memory": "${MEMORY_MYSQL_LIMIT}" +                  }                  }                }              ] diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/dancer-mysql-persistent.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/dancer-mysql-persistent.json new file mode 100644 index 000000000..074561550 --- /dev/null +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/dancer-mysql-persistent.json @@ -0,0 +1,519 @@ +{ +  "kind": "Template", +  "apiVersion": "v1", +  "metadata": { +    "name": "dancer-mysql-persistent", +    "annotations": { +      "openshift.io/display-name": "Dancer + MySQL (Persistent)", +      "description": "An example Dancer application with a MySQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/dancer-ex/blob/master/README.md.", +      "tags": "quickstart,perl,dancer", +      "iconClass": "icon-perl" +    } +  }, +  "message": "The following service(s) have been created in your project: ${NAME}, ${DATABASE_SERVICE_NAME}.\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/openshift/dancer-ex/blob/master/README.md.", +  "labels": { +    "template": "dancer-mysql-persistent" +  }, +  "objects": [ +    { +      "kind": "Secret", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "stringData" : { +        "database-user" : "${DATABASE_USER}", +        "database-password" : "${DATABASE_PASSWORD}", +        "keybase" : "${SECRET_KEY_BASE}" +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Exposes and load balances the application pods", +          "service.alpha.openshift.io/dependencies": "[{\"name\": \"${DATABASE_SERVICE_NAME}\", \"kind\": \"Service\"}]" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "web", +            "port": 8080, +            "targetPort": 8080 +          } +        ], +        "selector": { +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "Route", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "spec": { +        "host": "${APPLICATION_DOMAIN}", +        "to": { +          "kind": "Service", +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "ImageStream", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Keeps track of changes in the application image" +        } +      } +    }, +    { +      "kind": "BuildConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to build the application" +        } +      }, +      "spec": { +        "source": { +          "type": "Git", +          "git": { +            "uri": "${SOURCE_REPOSITORY_URL}", +            "ref": "${SOURCE_REPOSITORY_REF}" +          }, +          "contextDir": "${CONTEXT_DIR}" +        }, +        "strategy": { +          "type": "Source", +          "sourceStrategy": { +            "from": { +              "kind": "ImageStreamTag", +              "namespace": "${NAMESPACE}", +              "name": "perl:5.24" +            }, +            "env":  [ +              { +                  "name": "CPAN_MIRROR", +                  "value": "${CPAN_MIRROR}" +              } +            ] +          } +        }, +        "output": { +          "to": { +            "kind": "ImageStreamTag", +            "name": "${NAME}:latest" +          } +        }, +        "triggers": [ +          { +            "type": "ImageChange" +          }, +          { +            "type": "ConfigChange" +          }, +          { +            "type": "GitHub", +            "github": { +              "secret": "${GITHUB_WEBHOOK_SECRET}" +            } +          } +        ], +        "postCommit": { +          "script": "perl -I extlib/lib/perl5 -I lib t/*" +        } +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to deploy the application server" +        } +      }, +      "spec": { +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "dancer-mysql-persistent" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "name": "${NAME}:latest" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${NAME}", +            "labels": { +              "name": "${NAME}" +            } +          }, +          "spec": { +            "containers": [ +              { +                "name": "dancer-mysql-persistent", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 8080 +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 3, +                  "httpGet": { +                    "path": "/health", +                    "port": 8080 +                  } +                }, +                "livenessProbe": { +                    "timeoutSeconds": 3, +                    "initialDelaySeconds": 30, +                    "httpGet": { +                        "path": "/", +                        "port": 8080 +                    } +                }, +                "env": [ +                  { +                    "name": "DATABASE_SERVICE_NAME", +                    "value": "${DATABASE_SERVICE_NAME}" +                  }, +                  { +                    "name": "MYSQL_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "MYSQL_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                    "name": "MYSQL_DATABASE", +                    "value": "${DATABASE_NAME}" +                  }, +                  { +                    "name": "SECRET_KEY_BASE", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "keybase" +                      } +                    } +                  }, +                  { +                    "name": "PERL_APACHE2_RELOAD", +                    "value": "${PERL_APACHE2_RELOAD}" +                  } +                ], +                "resources": { +                  "limits": { +                    "memory": "${MEMORY_LIMIT}" +                  } +                } +              } +            ] +          } +        } +      } +    }, +    { +      "kind": "PersistentVolumeClaim", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}" +      }, +      "spec": { +        "accessModes": [ +          "ReadWriteOnce" +        ], +        "resources": { +          "requests": { +            "storage": "${VOLUME_CAPACITY}" +          } +        } +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Exposes the database server" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "mysql", +            "port": 3306, +            "targetPort": 3306 +          } +        ], +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        } +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Defines how to deploy the database" +        } +      }, +      "spec": { +        "strategy": { +          "type": "Recreate" +        }, +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "mysql" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "namespace": "${NAMESPACE}", +                "name": "mysql:5.7" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${DATABASE_SERVICE_NAME}", +            "labels": { +              "name": "${DATABASE_SERVICE_NAME}" +            } +          }, +          "spec": { +            "volumes": [ +              { +                "name": "${DATABASE_SERVICE_NAME}-data", +                "persistentVolumeClaim": { +                  "claimName": "${DATABASE_SERVICE_NAME}" +                } +              } +            ], +            "containers": [ +              { +                "name": "mysql", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 3306 +                  } +                ], +                "volumeMounts": [ +                  { +                    "name": "${DATABASE_SERVICE_NAME}-data", +                    "mountPath": "/var/lib/mysql/data" +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 5, +                  "exec": { +                    "command": [ "/bin/sh", "-i", "-c", "MYSQL_PWD='${DATABASE_PASSWORD}' mysql -h 127.0.0.1 -u ${DATABASE_USER} -D ${DATABASE_NAME} -e 'SELECT 1'" ] +                  } +                }, +                "livenessProbe": { +                    "timeoutSeconds": 1, +                    "initialDelaySeconds": 30, +                    "tcpSocket": { +                        "port": 3306 +                    } +                }, +                "env": [ +                  { +                    "name": "MYSQL_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "MYSQL_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                      "name": "MYSQL_DATABASE", +                      "value": "${DATABASE_NAME}" +                  } +                ], +                "resources": { +                    "limits": { +                        "memory": "${MEMORY_MYSQL_LIMIT}" +                    } +                } +              } +            ] +          } +        } +      } +    } +  ], +  "parameters": [ +    { +      "name": "NAME", +      "displayName": "Name", +      "description": "The name assigned to all of the frontend objects defined in this template.", +      "required": true, +      "value": "dancer-mysql-persistent" +    }, +    { +      "name": "NAMESPACE", +      "displayName": "Namespace", +      "description": "The OpenShift Namespace where the ImageStream resides.", +      "required": true, +      "value": "openshift" +    }, +    { +      "name": "MEMORY_LIMIT", +      "displayName": "Memory Limit", +      "description": "Maximum amount of memory the Perl Dancer container can use.", +      "required": true, +      "value": "512Mi" +    }, +    { +      "name": "MEMORY_MYSQL_LIMIT", +      "displayName": "Memory Limit (MySQL)", +      "description": "Maximum amount of memory the MySQL container can use.", +      "required": true, +      "value": "512Mi" +    }, +    { +      "name": "VOLUME_CAPACITY", +      "displayName": "Volume Capacity", +      "description": "Volume space available for data, e.g. 512Mi, 2Gi", +      "value": "1Gi", +      "required": true +    }, +    { +      "name": "SOURCE_REPOSITORY_URL", +      "displayName": "Git Repository URL", +      "description": "The URL of the repository with your application source code.", +      "required": true, +      "value": "https://github.com/openshift/dancer-ex.git" +    }, +    { +      "name": "SOURCE_REPOSITORY_REF", +      "displayName": "Git Reference", +      "description": "Set this to a branch name, tag or other ref of your repository if you are not using the default branch." +    }, +    { +      "name": "CONTEXT_DIR", +      "displayName": "Context Directory", +      "description": "Set this to the relative path to your project if it is not in the root of your repository." +    }, +    { +      "name": "APPLICATION_DOMAIN", +      "displayName": "Application Hostname", +      "description": "The exposed hostname that will route to the Dancer service, if left blank a value will be defaulted.", +      "value": "" +    }, +    { +      "name": "GITHUB_WEBHOOK_SECRET", +      "displayName": "GitHub Webhook Secret", +      "description": "A secret string used to configure the GitHub webhook.", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{40}" +    }, +    { +      "name": "DATABASE_SERVICE_NAME", +      "displayName": "Database Service Name", +      "required": true, +      "value": "database" +    }, +    { +      "name": "DATABASE_USER", +      "displayName": "Database Username", +      "generate": "expression", +      "from": "user[A-Z0-9]{3}" +    }, +    { +      "name": "DATABASE_PASSWORD", +      "displayName": "Database Password", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{8}" +    }, +    { +      "name": "DATABASE_NAME", +      "displayName": "Database Name", +      "required": true, +      "value": "sampledb" +    }, +    { +      "name": "PERL_APACHE2_RELOAD", +      "displayName": "Perl Module Reload", +      "description": "Set this to \"true\" to enable automatic reloading of modified Perl modules.", +      "value": "" +    }, +    { +      "name": "SECRET_KEY_BASE", +      "displayName": "Secret Key", +      "description": "Your secret key for verifying the integrity of signed cookies.", +      "generate": "expression", +      "from": "[a-z0-9]{127}" +    }, +    { +      "name": "CPAN_MIRROR", +      "displayName": "Custom CPAN Mirror URL", +      "description": "The custom CPAN mirror URL", +      "value": "" +    } +  ] +} diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/dancer-mysql.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/dancer-mysql.json index dccb8bf7f..18100974b 100644 --- a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/dancer-mysql.json +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/dancer-mysql.json @@ -22,8 +22,9 @@          "name": "${NAME}"        },        "stringData" : { -        "databaseUser" : "${DATABASE_USER}", -        "databasePassword" : "${DATABASE_PASSWORD}" +        "database-user" : "${DATABASE_USER}", +        "database-password" : "${DATABASE_PASSWORD}", +        "keybase" : "${SECRET_KEY_BASE}"        }      },      { @@ -97,7 +98,7 @@              "from": {                "kind": "ImageStreamTag",                "namespace": "${NAMESPACE}", -              "name": "perl:5.20" +              "name": "perl:5.24"              },              "env":  [                { @@ -207,7 +208,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseUser" +                        "key" : "database-user"                        }                      }                    }, @@ -216,7 +217,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databasePassword" +                        "key" : "database-password"                        }                      }                    }, @@ -226,7 +227,12 @@                    },                    {                      "name": "SECRET_KEY_BASE", -                    "value": "${SECRET_KEY_BASE}" +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "keybase" +                      } +                    }                    },                    {                      "name": "PERL_APACHE2_RELOAD", @@ -290,7 +296,7 @@                "from": {                  "kind": "ImageStreamTag",                  "namespace": "${NAMESPACE}", -                "name": "mysql:5.6" +                "name": "mysql:5.7"                }              }            }, @@ -351,7 +357,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseUser" +                        "key" : "database-user"                        }                      }                    }, @@ -360,7 +366,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databasePassword" +                        "key" : "database-password"                        }                      }                    }, diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/django-postgresql-persistent.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/django-postgresql-persistent.json new file mode 100644 index 000000000..b39771bd8 --- /dev/null +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/django-postgresql-persistent.json @@ -0,0 +1,532 @@ +{ +  "kind": "Template", +  "apiVersion": "v1", +  "metadata": { +    "name": "django-psql-persistent", +    "annotations": { +      "openshift.io/display-name": "Django + PostgreSQL (Persistent)", +      "description": "An example Django application with a PostgreSQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/django-ex/blob/master/README.md.", +      "tags": "quickstart,python,django", +      "iconClass": "icon-python" +    } +  }, +  "message": "The following service(s) have been created in your project: ${NAME}, ${DATABASE_SERVICE_NAME}.\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/openshift/django-ex/blob/master/README.md.", +  "labels": { +    "template": "django-psql-persistent" +  }, +  "objects": [ +    { +      "kind": "Secret", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "stringData" : { +        "database-user" : "${DATABASE_USER}", +        "database-password" : "${DATABASE_PASSWORD}", +        "django-secret-key" : "${DJANGO_SECRET_KEY}" +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Exposes and load balances the application pods", +          "service.alpha.openshift.io/dependencies": "[{\"name\": \"${DATABASE_SERVICE_NAME}\", \"kind\": \"Service\"}]" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "web", +            "port": 8080, +            "targetPort": 8080 +          } +        ], +        "selector": { +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "Route", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "spec": { +        "host": "${APPLICATION_DOMAIN}", +        "to": { +          "kind": "Service", +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "ImageStream", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Keeps track of changes in the application image" +        } +      } +    }, +    { +      "kind": "BuildConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to build the application" +        } +      }, +      "spec": { +        "source": { +          "type": "Git", +          "git": { +            "uri": "${SOURCE_REPOSITORY_URL}", +            "ref": "${SOURCE_REPOSITORY_REF}" +          }, +          "contextDir": "${CONTEXT_DIR}" +        }, +        "strategy": { +          "type": "Source", +          "sourceStrategy": { +            "from": { +              "kind": "ImageStreamTag", +              "namespace": "${NAMESPACE}", +              "name": "python:3.5" +            }, +            "env": [ +              { +                  "name": "PIP_INDEX_URL", +                  "value": "${PIP_INDEX_URL}" +              } +            ] +          } +        }, +        "output": { +          "to": { +            "kind": "ImageStreamTag", +            "name": "${NAME}:latest" +          } +        }, +        "triggers": [ +          { +            "type": "ImageChange" +          }, +          { +            "type": "ConfigChange" +          }, +          { +            "type": "GitHub", +            "github": { +              "secret": "${GITHUB_WEBHOOK_SECRET}" +            } +          } +        ], +        "postCommit": { +           "script": "./manage.py test" +        } +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to deploy the application server" +        } +      }, +      "spec": { +        "strategy": { +          "type": "Rolling" +        }, +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "django-psql-persistent" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "name": "${NAME}:latest" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${NAME}", +            "labels": { +              "name": "${NAME}" +            } +          }, +          "spec": { +            "containers": [ +              { +                "name": "django-psql-persistent", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 8080 +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 3, +                  "httpGet": { +                    "path": "/health", +                    "port": 8080 +                  } +                }, +                "livenessProbe": { +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 30, +                  "httpGet": { +                    "path": "/health", +                    "port": 8080 +                  } +                }, +                "env": [ +                  { +                    "name": "DATABASE_SERVICE_NAME", +                    "value": "${DATABASE_SERVICE_NAME}" +                  }, +                  { +                    "name": "DATABASE_ENGINE", +                    "value": "${DATABASE_ENGINE}" +                  }, +                  { +                    "name": "DATABASE_NAME", +                    "value": "${DATABASE_NAME}" +                  }, +                  { +                    "name": "DATABASE_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "DATABASE_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                    "name": "APP_CONFIG", +                    "value": "${APP_CONFIG}" +                  }, +                  { +                    "name": "DJANGO_SECRET_KEY", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "django-secret-key" +                      } +                    } +                  } +                ], +                "resources": { +                  "limits": { +                    "memory": "${MEMORY_LIMIT}" +                  } +                } +              } +            ] +          } +        } +      } +    }, +    { +      "kind": "PersistentVolumeClaim", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}" +      }, +      "spec": { +        "accessModes": [ +          "ReadWriteOnce" +        ], +        "resources": { +          "requests": { +            "storage": "${VOLUME_CAPACITY}" +          } +        } +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Exposes the database server" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "postgresql", +            "port": 5432, +            "targetPort": 5432 +          } +        ], +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        } +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Defines how to deploy the database" +        } +      }, +      "spec": { +        "strategy": { +          "type": "Recreate" +        }, +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "postgresql" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "namespace": "${NAMESPACE}", +                "name": "postgresql:9.5" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${DATABASE_SERVICE_NAME}", +            "labels": { +              "name": "${DATABASE_SERVICE_NAME}" +            } +          }, +          "spec": { +            "volumes": [ +              { +                "name": "${DATABASE_SERVICE_NAME}-data", +                "persistentVolumeClaim": { +                  "claimName": "${DATABASE_SERVICE_NAME}" +                } +              } +            ], +            "containers": [ +              { +                "name": "postgresql", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 5432 +                  } +                ], +                "env": [ +                  { +                    "name": "POSTGRESQL_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "POSTGRESQL_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                    "name": "POSTGRESQL_DATABASE", +                    "value": "${DATABASE_NAME}" +                  } +                ], +                "volumeMounts": [ +                  { +                    "name": "${DATABASE_SERVICE_NAME}-data", +                    "mountPath": "/var/lib/pgsql/data" +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 5, +                  "exec": { +                    "command": [ "/bin/sh", "-i", "-c", "psql -h 127.0.0.1 -U ${POSTGRESQL_USER} -q -d ${POSTGRESQL_DATABASE} -c 'SELECT 1'"] +                  } +                }, +                "livenessProbe": { +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 30, +                  "tcpSocket": { +                    "port": 5432 +                  } +                }, +                "resources": { +                  "limits": { +                    "memory": "${MEMORY_POSTGRESQL_LIMIT}" +                  } +                } +              } +            ] +          } +        } +      } +    } +  ], +  "parameters": [ +    { +      "name": "NAME", +      "displayName": "Name", +      "description": "The name assigned to all of the frontend objects defined in this template.", +      "required": true, +      "value": "django-psql-persistent" +    }, +    { +      "name": "NAMESPACE", +      "displayName": "Namespace", +      "required": true, +      "description": "The OpenShift Namespace where the ImageStream resides.", +      "value": "openshift" +    }, +    { +      "name": "MEMORY_LIMIT", +      "displayName": "Memory Limit", +      "required": true, +      "description": "Maximum amount of memory the Django container can use.", +      "value": "512Mi" +    }, +    { +      "name": "MEMORY_POSTGRESQL_LIMIT", +      "displayName": "Memory Limit (PostgreSQL)", +      "required": true, +      "description": "Maximum amount of memory the PostgreSQL container can use.", +      "value": "512Mi" +    }, +    { +      "name": "VOLUME_CAPACITY", +      "displayName": "Volume Capacity", +      "description": "Volume space available for data, e.g. 512Mi, 2Gi", +      "value": "1Gi", +      "required": true +    }, +    { +      "name": "SOURCE_REPOSITORY_URL", +      "displayName": "Git Repository URL", +      "required": true, +      "description": "The URL of the repository with your application source code.", +      "value": "https://github.com/openshift/django-ex.git" +    }, +    { +      "name": "SOURCE_REPOSITORY_REF", +      "displayName": "Git Reference", +      "description": "Set this to a branch name, tag or other ref of your repository if you are not using the default branch." +    }, +    { +      "name": "CONTEXT_DIR", +      "displayName": "Context Directory", +      "description": "Set this to the relative path to your project if it is not in the root of your repository." +    }, +    { +      "name": "APPLICATION_DOMAIN", +      "displayName": "Application Hostname", +      "description": "The exposed hostname that will route to the Django service, if left blank a value will be defaulted.", +      "value": "" +    }, +    { +      "name": "GITHUB_WEBHOOK_SECRET", +      "displayName": "GitHub Webhook Secret", +      "description": "A secret string used to configure the GitHub webhook.", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{40}" +    }, +    { +      "name": "DATABASE_SERVICE_NAME", +      "displayName": "Database Service Name", +      "required": true, +      "value": "postgresql" +    }, +    { +      "name": "DATABASE_ENGINE", +      "displayName": "Database Engine", +      "required": true, +      "description": "Database engine: postgresql, mysql or sqlite (default).", +      "value": "postgresql" +    }, +    { +      "name": "DATABASE_NAME", +      "displayName": "Database Name", +      "required": true, +      "value": "default" +    }, +    { +      "name": "DATABASE_USER", +      "displayName": "Database Username", +      "required": true, +      "value": "django" +    }, +    { +      "name": "DATABASE_PASSWORD", +      "displayName": "Database User Password", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{16}" +    }, +    { +      "name": "APP_CONFIG", +      "displayName": "Application Configuration File Path", +      "description": "Relative path to Gunicorn configuration file (optional)." +    }, +    { +      "name": "DJANGO_SECRET_KEY", +      "displayName": "Django Secret Key", +      "description": "Set this to a long random string.", +      "generate": "expression", +      "from": "[\\w]{50}" +    }, +    { +      "name": "PIP_INDEX_URL", +      "displayName": "Custom PyPi Index URL", +      "description": "The custom PyPi index URL", +      "value": "" +    } +  ] +} diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/django-postgresql.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/django-postgresql.json index 59ff8a988..64b914e61 100644 --- a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/django-postgresql.json +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/django-postgresql.json @@ -22,8 +22,9 @@          "name": "${NAME}"        },        "stringData" : { -        "databaseUser" : "${DATABASE_USER}", -        "databasePassword" : "${DATABASE_PASSWORD}" +        "database-user" : "${DATABASE_USER}", +        "database-password" : "${DATABASE_PASSWORD}", +        "django-secret-key" : "${DJANGO_SECRET_KEY}"        }      },      { @@ -218,7 +219,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseUser" +                        "key" : "database-user"                        }                      }                    }, @@ -227,7 +228,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databasePassword" +                        "key" : "database-password"                        }                      }                    }, @@ -237,7 +238,12 @@                    },                    {                      "name": "DJANGO_SECRET_KEY", -                    "value": "${DJANGO_SECRET_KEY}" +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "django-secret-key" +                      } +                    }                    }                  ],                  "resources": { @@ -338,7 +344,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseUser" +                        "key" : "database-user"                        }                      }                    }, @@ -347,7 +353,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databasePassword" +                        "key" : "database-password"                        }                      }                    }, diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/nodejs-mongodb-persistent.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/nodejs-mongodb-persistent.json new file mode 100644 index 000000000..fecb84662 --- /dev/null +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/nodejs-mongodb-persistent.json @@ -0,0 +1,541 @@ +{ +  "kind": "Template", +  "apiVersion": "v1", +  "metadata": { +    "name": "nodejs-mongo-persistent", +    "annotations": { +      "openshift.io/display-name": "Node.js + MongoDB (Persistent)", +      "description": "An example Node.js application with a MongoDB database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/nodejs-ex/blob/master/README.md.", +      "tags": "quickstart,nodejs", +      "iconClass": "icon-nodejs" +    } +  }, +  "message": "The following service(s) have been created in your project: ${NAME}, ${DATABASE_SERVICE_NAME}.\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/openshift/nodejs-ex/blob/master/README.md.", +  "labels": { +    "template": "nodejs-mongo-persistent" +  }, +  "objects": [ +    { +      "kind": "Secret", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "stringData": { +        "database-user": "${DATABASE_USER}", +        "database-password": "${DATABASE_PASSWORD}", +        "database-admin-password" : "${DATABASE_ADMIN_PASSWORD}" +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Exposes and load balances the application pods", +          "service.alpha.openshift.io/dependencies": "[{\"name\": \"${DATABASE_SERVICE_NAME}\", \"kind\": \"Service\"}]" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "web", +            "port": 8080, +            "targetPort": 8080 +          } +        ], +        "selector": { +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "Route", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "spec": { +        "host": "${APPLICATION_DOMAIN}", +        "to": { +          "kind": "Service", +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "ImageStream", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Keeps track of changes in the application image" +        } +      } +    }, +    { +      "kind": "BuildConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to build the application" +        } +      }, +      "spec": { +        "source": { +          "type": "Git", +          "git": { +            "uri": "${SOURCE_REPOSITORY_URL}", +            "ref": "${SOURCE_REPOSITORY_REF}" +          }, +          "contextDir": "${CONTEXT_DIR}" +        }, +        "strategy": { +          "type": "Source", +          "sourceStrategy": { +            "from": { +              "kind": "ImageStreamTag", +              "namespace": "${NAMESPACE}", +              "name": "nodejs:4" +            }, +            "env":  [ +              { +                "name": "NPM_MIRROR", +                "value": "${NPM_MIRROR}" +              } +            ] +          } +        }, +        "output": { +          "to": { +            "kind": "ImageStreamTag", +            "name": "${NAME}:latest" +          } +        }, +        "triggers": [ +          { +            "type": "ImageChange" +          }, +          { +            "type": "ConfigChange" +          }, +          { +            "type": "GitHub", +            "github": { +              "secret": "${GITHUB_WEBHOOK_SECRET}" +            } +          }, +          { +            "type": "Generic", +            "generic": { +              "secret": "${GENERIC_WEBHOOK_SECRET}" +            } +          } +        ], +        "postCommit": { +          "script": "npm test" +        } +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to deploy the application server" +        } +      }, +      "spec": { +        "strategy": { +          "type": "Rolling" +        }, +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "nodejs-mongo-persistent" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "name": "${NAME}:latest" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${NAME}", +            "labels": { +              "name": "${NAME}" +            } +          }, +          "spec": { +            "containers": [ +              { +                "name": "nodejs-mongo-persistent", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 8080 +                  } +                ], +                "env": [ +                  { +                    "name": "DATABASE_SERVICE_NAME", +                    "value": "${DATABASE_SERVICE_NAME}" +                  }, +                  { +                    "name": "MONGODB_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "MONGODB_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                    "name": "MONGODB_DATABASE", +                    "value": "${DATABASE_NAME}" +                  }, +                  { +                    "name": "MONGODB_ADMIN_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-admin-password" +                      } +                    } +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 3, +                  "httpGet": { +                    "path": "/pagecount", +                    "port": 8080 +                  } +                }, +                "livenessProbe": { +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 30, +                  "httpGet": { +                    "path": "/pagecount", +                    "port": 8080 +                  } +                }, +                "resources": { +                  "limits": { +                    "memory": "${MEMORY_LIMIT}" +                  } +                } +              } +            ] +          } +        } +      } +    }, +    { +      "kind": "PersistentVolumeClaim", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}" +      }, +      "spec": { +        "accessModes": [ +          "ReadWriteOnce" +        ], +        "resources": { +          "requests": { +            "storage": "${VOLUME_CAPACITY}" +          } +        } +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Exposes the database server" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "mongodb", +            "port": 27017, +            "targetPort": 27017 +          } +        ], +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        } +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Defines how to deploy the database" +        } +      }, +      "spec": { +        "strategy": { +          "type": "Recreate" +        }, +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "mongodb" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "namespace": "${NAMESPACE}", +                "name": "mongodb:3.2" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${DATABASE_SERVICE_NAME}", +            "labels": { +              "name": "${DATABASE_SERVICE_NAME}" +            } +          }, +          "spec": { +            "containers": [ +              { +                "name": "mongodb", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 27017 +                  } +                ], +                "env": [ +                  { +                    "name": "MONGODB_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "MONGODB_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                    "name": "MONGODB_DATABASE", +                    "value": "${DATABASE_NAME}" +                  }, +                  { +                    "name": "MONGODB_ADMIN_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-admin-password" +                      } +                    } +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 3, +                  "exec": { +                    "command": [ +                      "/bin/sh", +                      "-i", +                      "-c", +                      "mongo 127.0.0.1:27017/$MONGODB_DATABASE -u $MONGODB_USER -p $MONGODB_PASSWORD --eval=\"quit()\"" +                    ] +                  } +                }, +                "livenessProbe": { +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 30, +                  "tcpSocket": { +                    "port": 27017 +                  } +                }, +                "resources": { +                  "limits": { +                    "memory": "${MEMORY_MONGODB_LIMIT}" +                  } +                }, +                "volumeMounts": [ +                  { +                    "name": "${DATABASE_SERVICE_NAME}-data", +                    "mountPath": "/var/lib/mongodb/data" +                  } +                ] +              } +            ], +            "volumes": [ +              { +                "name": "${DATABASE_SERVICE_NAME}-data", +                "persistentVolumeClaim": { +                  "claimName": "${DATABASE_SERVICE_NAME}" +                } +              } +            ] +          } +        } +      } +    } +  ], +  "parameters": [ +    { +      "name": "NAME", +      "displayName": "Name", +      "description": "The name assigned to all of the frontend objects defined in this template.", +      "required": true, +      "value": "nodejs-mongo-persistent" +    }, +    { +      "name": "NAMESPACE", +      "displayName": "Namespace", +      "description": "The OpenShift Namespace where the ImageStream resides.", +      "required": true, +      "value": "openshift" +    }, +    { +      "name": "MEMORY_LIMIT", +      "displayName": "Memory Limit", +      "description": "Maximum amount of memory the Node.js container can use.", +      "required": true, +      "value": "512Mi" +    }, +    { +      "name": "MEMORY_MONGODB_LIMIT", +      "displayName": "Memory Limit (MongoDB)", +      "description": "Maximum amount of memory the MongoDB container can use.", +      "required": true, +      "value": "512Mi" +    }, +    { +      "name": "VOLUME_CAPACITY", +      "displayName": "Volume Capacity", +      "description": "Volume space available for data, e.g. 512Mi, 2Gi", +      "value": "1Gi", +      "required": true +    }, +    { +      "name": "SOURCE_REPOSITORY_URL", +      "displayName": "Git Repository URL", +      "description": "The URL of the repository with your application source code.", +      "required": true, +      "value": "https://github.com/openshift/nodejs-ex.git" +    }, +    { +      "name": "SOURCE_REPOSITORY_REF", +      "displayName": "Git Reference", +      "description": "Set this to a branch name, tag or other ref of your repository if you are not using the default branch." +    }, +    { +      "name": "CONTEXT_DIR", +      "displayName": "Context Directory", +      "description": "Set this to the relative path to your project if it is not in the root of your repository." +    }, +    { +      "name": "APPLICATION_DOMAIN", +      "displayName": "Application Hostname", +      "description": "The exposed hostname that will route to the Node.js service, if left blank a value will be defaulted.", +      "value": "" +    }, +    { +      "name": "GITHUB_WEBHOOK_SECRET", +      "displayName": "GitHub Webhook Secret", +      "description": "A secret string used to configure the GitHub webhook.", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{40}" +    }, +    { +      "name": "GENERIC_WEBHOOK_SECRET", +      "displayName": "Generic Webhook Secret", +      "description": "A secret string used to configure the Generic webhook.", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{40}" +    }, +    { +      "name": "DATABASE_SERVICE_NAME", +      "displayName": "Database Service Name", +      "required": true, +      "value": "mongodb" +    }, +    { +      "name": "DATABASE_USER", +      "displayName": "MongoDB Username", +      "description": "Username for MongoDB user that will be used for accessing the database.", +      "generate": "expression", +      "from": "user[A-Z0-9]{3}" +    }, +    { +      "name": "DATABASE_PASSWORD", +      "displayName": "MongoDB Password", +      "description": "Password for the MongoDB user.", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{16}" +    }, +    { +      "name": "DATABASE_NAME", +      "displayName": "Database Name", +      "required": true, +      "value": "sampledb" +    }, +    { +      "name": "DATABASE_ADMIN_PASSWORD", +      "displayName": "Database Administrator Password", +      "description": "Password for the database admin user.", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{16}" +    }, +    { +      "name": "NPM_MIRROR", +      "displayName": "Custom NPM Mirror URL", +      "description": "The custom NPM mirror URL", +      "value": "" +    } +  ] +} diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/nodejs-mongodb.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/nodejs-mongodb.json index 91f9ec7b3..6a55f0251 100644 --- a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/nodejs-mongodb.json +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/nodejs-mongodb.json @@ -22,9 +22,9 @@          "name": "${NAME}"        },        "stringData": { -        "databaseUser": "${DATABASE_USER}", -        "databasePassword": "${DATABASE_PASSWORD}", -        "databaseAdminPassword" : "${DATABASE_ADMIN_PASSWORD}" +        "database-user": "${DATABASE_USER}", +        "database-password": "${DATABASE_PASSWORD}", +        "database-admin-password" : "${DATABASE_ADMIN_PASSWORD}"        }      },      { @@ -201,7 +201,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseUser" +                        "key" : "database-user"                        }                      }                    }, @@ -210,7 +210,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databasePassword" +                        "key" : "database-password"                        }                      }                    }, @@ -223,7 +223,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseAdminPassword" +                        "key" : "database-admin-password"                        }                      }                    } @@ -336,7 +336,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseUser" +                        "key" : "database-user"                        }                      }                    }, @@ -345,7 +345,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databasePassword" +                        "key" : "database-password"                        }                      }                    }, @@ -358,7 +358,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseAdminPassword" +                        "key" : "database-admin-password"                        }                      }                    } diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/rails-postgresql-persistent.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/rails-postgresql-persistent.json new file mode 100644 index 000000000..6c0a484b5 --- /dev/null +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/rails-postgresql-persistent.json @@ -0,0 +1,598 @@ +{ +  "kind": "Template", +  "apiVersion": "v1", +  "metadata": { +    "name": "rails-pgsql-persistent", +    "annotations": { +      "openshift.io/display-name": "Rails + PostgreSQL (Persistent)", +      "description": "An example Rails application with a PostgreSQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/rails-ex/blob/master/README.md.", +      "tags": "quickstart,ruby,rails", +      "iconClass": "icon-ruby" +    } +  }, +  "message": "The following service(s) have been created in your project: ${NAME}, ${DATABASE_SERVICE_NAME}.\n\nFor more information about using this template, including OpenShift considerations, see https://github.com/openshift/rails-ex/blob/master/README.md.", +  "labels": { +    "template": "rails-pgsql-persistent" +  }, +  "objects": [ +    { +      "kind": "Secret", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "stringData" : { +        "database-user" : "${DATABASE_USER}", +        "database-password" : "${DATABASE_PASSWORD}", +        "application-user" : "${APPLICATION_USER}", +        "application-password" : "${APPLICATION_PASSWORD}", +        "keybase" : "${SECRET_KEY_BASE}" +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Exposes and load balances the application pods", +          "service.alpha.openshift.io/dependencies": "[{\"name\": \"${DATABASE_SERVICE_NAME}\", \"kind\": \"Service\"}]" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "web", +            "port": 8080, +            "targetPort": 8080 +          } +        ], +        "selector": { +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "Route", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}" +      }, +      "spec": { +        "host": "${APPLICATION_DOMAIN}", +        "to": { +          "kind": "Service", +          "name": "${NAME}" +        } +      } +    }, +    { +      "kind": "ImageStream", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Keeps track of changes in the application image" +        } +      } +    }, +    { +      "kind": "BuildConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to build the application" +        } +      }, +      "spec": { +        "source": { +          "type": "Git", +          "git": { +            "uri": "${SOURCE_REPOSITORY_URL}", +            "ref": "${SOURCE_REPOSITORY_REF}" +          }, +          "contextDir": "${CONTEXT_DIR}" +        }, +        "strategy": { +          "type": "Source", +          "sourceStrategy": { +            "from": { +              "kind": "ImageStreamTag", +              "namespace": "${NAMESPACE}", +              "name": "ruby:2.3" +            }, +            "env": [ +              { +                "name": "RUBYGEM_MIRROR", +                "value": "${RUBYGEM_MIRROR}" +              } +            ] +          } +        }, +        "output": { +          "to": { +            "kind": "ImageStreamTag", +            "name": "${NAME}:latest" +          } +        }, +        "triggers": [ +          { +            "type": "ImageChange" +          }, +          { +            "type": "ConfigChange" +          }, +          { +            "type": "GitHub", +            "github": { +              "secret": "${GITHUB_WEBHOOK_SECRET}" +            } +          } +        ], +        "postCommit": { +          "script": "bundle exec rake test" +        } +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${NAME}", +        "annotations": { +          "description": "Defines how to deploy the application server" +        } +      }, +      "spec": { +        "strategy": { +          "type": "Recreate", +          "recreateParams": { +            "pre": { +              "failurePolicy": "Abort", +              "execNewPod": { +                "command": [ +                  "./migrate-database.sh" +                ], +                "containerName": "${NAME}" +              } +            } +          } +        }, +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "rails-pgsql-persistent" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "name": "${NAME}:latest" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${NAME}", +            "labels": { +              "name": "${NAME}" +            } +          }, +          "spec": { +            "containers": [ +              { +                "name": "rails-pgsql-persistent", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 8080 +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 5, +                  "httpGet": { +                    "path": "/articles", +                    "port": 8080 +                  } +                }, +                "livenessProbe": { +                  "timeoutSeconds": 3, +                  "initialDelaySeconds": 10, +                  "httpGet": { +                    "path": "/articles", +                    "port": 8080 +                  } +                }, +                "env": [ +                  { +                    "name": "DATABASE_SERVICE_NAME", +                    "value": "${DATABASE_SERVICE_NAME}" +                  }, +                  { +                    "name": "POSTGRESQL_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "POSTGRESQL_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                    "name": "SECRET_KEY_BASE", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "keybase" +                      } +                    } +                  }, +                  { +                    "name": "POSTGRESQL_DATABASE", +                    "value": "${DATABASE_NAME}" +                  }, +                  { +                    "name": "POSTGRESQL_MAX_CONNECTIONS", +                    "value": "${POSTGRESQL_MAX_CONNECTIONS}" +                  }, +                  { +                    "name": "POSTGRESQL_SHARED_BUFFERS", +                    "value": "${POSTGRESQL_SHARED_BUFFERS}" +                  }, +                  { +                    "name": "APPLICATION_DOMAIN", +                    "value": "${APPLICATION_DOMAIN}" +                  }, +                  { +                    "name": "APPLICATION_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "application-user" +                      } +                    } +                  }, +                  { +                    "name": "APPLICATION_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "application-password" +                      } +                    } +                  }, +                  { +                    "name": "RAILS_ENV", +                    "value": "${RAILS_ENV}" +                  } +                ], +                "resources": { +                  "limits": { +                    "memory": "${MEMORY_LIMIT}" +                  } +                } +              } +            ] +          } +        } +      } +    }, +    { +      "kind": "PersistentVolumeClaim", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}" +      }, +      "spec": { +        "accessModes": [ +          "ReadWriteOnce" +        ], +        "resources": { +          "requests": { +            "storage": "${VOLUME_CAPACITY}" +          } +        } +      } +    }, +    { +      "kind": "Service", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Exposes the database server" +        } +      }, +      "spec": { +        "ports": [ +          { +            "name": "postgresql", +            "port": 5432, +            "targetPort": 5432 +          } +        ], +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        } +      } +    }, +    { +      "kind": "DeploymentConfig", +      "apiVersion": "v1", +      "metadata": { +        "name": "${DATABASE_SERVICE_NAME}", +        "annotations": { +          "description": "Defines how to deploy the database" +        } +      }, +      "spec": { +        "strategy": { +          "type": "Recreate" +        }, +        "triggers": [ +          { +            "type": "ImageChange", +            "imageChangeParams": { +              "automatic": true, +              "containerNames": [ +                "postgresql" +              ], +              "from": { +                "kind": "ImageStreamTag", +                "namespace": "${NAMESPACE}", +                "name": "postgresql:9.5" +              } +            } +          }, +          { +            "type": "ConfigChange" +          } +        ], +        "replicas": 1, +        "selector": { +          "name": "${DATABASE_SERVICE_NAME}" +        }, +        "template": { +          "metadata": { +            "name": "${DATABASE_SERVICE_NAME}", +            "labels": { +              "name": "${DATABASE_SERVICE_NAME}" +            } +          }, +          "spec": { +            "volumes": [ +              { +                "name": "${DATABASE_SERVICE_NAME}-data", +                "persistentVolumeClaim": { +                  "claimName": "${DATABASE_SERVICE_NAME}" +                } +              } +            ], +            "containers": [ +              { +                "name": "postgresql", +                "image": " ", +                "ports": [ +                  { +                    "containerPort": 5432 +                  } +                ], +                "readinessProbe": { +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 5, +                  "exec": { +                    "command": [ "/bin/sh", "-i", "-c", "psql -h 127.0.0.1 -U ${POSTGRESQL_USER} -q -d ${POSTGRESQL_DATABASE} -c 'SELECT 1'"] +                  } +                }, +                "livenessProbe": { +                  "timeoutSeconds": 1, +                  "initialDelaySeconds": 30, +                  "tcpSocket": { +                    "port": 5432 +                  } +                }, +                "volumeMounts": [ +                  { +                    "name": "${DATABASE_SERVICE_NAME}-data", +                    "mountPath": "/var/lib/pgsql/data" +                  } +                ], +                "env": [ +                  { +                    "name": "POSTGRESQL_USER", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    } +                  }, +                  { +                    "name": "POSTGRESQL_PASSWORD", +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    } +                  }, +                  { +                    "name": "POSTGRESQL_DATABASE", +                    "value": "${DATABASE_NAME}" +                  }, +                  { +                    "name": "POSTGRESQL_MAX_CONNECTIONS", +                    "value": "${POSTGRESQL_MAX_CONNECTIONS}" +                  }, +                  { +                    "name": "POSTGRESQL_SHARED_BUFFERS", +                    "value": "${POSTGRESQL_SHARED_BUFFERS}" +                  } +                ], +                "resources": { +                  "limits": { +                    "memory": "${MEMORY_POSTGRESQL_LIMIT}" +                  } +                } +              } +            ] +          } +        } +      } +    } +  ], +  "parameters": [ +    { +      "name": "NAME", +      "displayName": "Name", +      "description": "The name assigned to all of the frontend objects defined in this template.", +      "required": true, +      "value": "rails-pgsql-persistent" +    }, +    { +      "name": "NAMESPACE", +      "displayName": "Namespace", +      "required": true, +      "description": "The OpenShift Namespace where the ImageStream resides.", +      "value": "openshift" +    }, +    { +      "name": "MEMORY_LIMIT", +      "displayName": "Memory Limit", +      "required": true, +      "description": "Maximum amount of memory the Rails container can use.", +      "value": "512Mi" +    }, +    { +      "name": "MEMORY_POSTGRESQL_LIMIT", +      "displayName": "Memory Limit (PostgreSQL)", +      "required": true, +      "description": "Maximum amount of memory the PostgreSQL container can use.", +      "value": "512Mi" +    }, +    { +      "name": "VOLUME_CAPACITY", +      "displayName": "Volume Capacity", +      "description": "Volume space available for data, e.g. 512Mi, 2Gi", +      "value": "1Gi", +      "required": true +    }, +    { +      "name": "SOURCE_REPOSITORY_URL", +      "displayName": "Git Repository URL", +      "required": true, +      "description": "The URL of the repository with your application source code.", +      "value": "https://github.com/openshift/rails-ex.git" +    }, +    { +      "name": "SOURCE_REPOSITORY_REF", +      "displayName": "Git Reference", +      "description": "Set this to a branch name, tag or other ref of your repository if you are not using the default branch." +    }, +    { +      "name": "CONTEXT_DIR", +      "displayName": "Context Directory", +      "description": "Set this to the relative path to your project if it is not in the root of your repository." +    }, +    { +      "name": "APPLICATION_DOMAIN", +      "displayName": "Application Hostname", +      "description": "The exposed hostname that will route to the Rails service, if left blank a value will be defaulted.", +      "value": "" +    }, +    { +      "name": "GITHUB_WEBHOOK_SECRET", +      "displayName": "GitHub Webhook Secret", +      "description": "A secret string used to configure the GitHub webhook.", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{40}" +    }, +    { +      "name": "SECRET_KEY_BASE", +      "displayName": "Secret Key", +      "description": "Your secret key for verifying the integrity of signed cookies.", +      "generate": "expression", +      "from": "[a-z0-9]{127}" +    }, +    { +      "name": "APPLICATION_USER", +      "displayName": "Application Username", +      "required": true, +      "description": "The application user that is used within the sample application to authorize access on pages.", +      "value": "openshift" +    }, +    { +      "name": "APPLICATION_PASSWORD", +      "displayName": "Application Password", +      "required": true, +      "description": "The application password that is used within the sample application to authorize access on pages.", +      "value": "secret" +    }, +    { +      "name": "RAILS_ENV", +      "displayName": "Rails Environment", +      "required": true, +      "description": "Environment under which the sample application will run. Could be set to production, development or test.", +      "value": "production" +    }, +    { +      "name": "DATABASE_SERVICE_NAME", +      "required": true, +      "displayName": "Database Service Name", +      "value": "postgresql" +    }, +    { +      "name": "DATABASE_USER", +      "displayName": "Database Username", +      "generate": "expression", +      "from": "user[A-Z0-9]{3}" +    }, +    { +      "name": "DATABASE_PASSWORD", +      "displayName": "Database Password", +      "generate": "expression", +      "from": "[a-zA-Z0-9]{8}" +    }, +    { +      "name": "DATABASE_NAME", +      "required": true, +      "displayName": "Database Name", +      "value": "root" +    }, +    { +      "name": "POSTGRESQL_MAX_CONNECTIONS", +      "displayName": "Maximum Database Connections", +      "value": "100" +    }, +    { +      "name": "POSTGRESQL_SHARED_BUFFERS", +      "displayName": "Shared Buffer Amount", +      "value": "12MB" +    }, +    { +      "name": "RUBYGEM_MIRROR", +      "displayName": "Custom RubyGems Mirror URL", +      "description": "The custom RubyGems mirror URL", +      "value": "" +    } +  ] +} diff --git a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/rails-postgresql.json b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/rails-postgresql.json index 6373562c4..043554c79 100644 --- a/roles/openshift_examples/files/examples/v1.4/quickstart-templates/rails-postgresql.json +++ b/roles/openshift_examples/files/examples/v1.4/quickstart-templates/rails-postgresql.json @@ -22,11 +22,11 @@          "name": "${NAME}"        },        "stringData" : { -        "databaseUser" : "${DATABASE_USER}", -        "databasePassword" : "${DATABASE_PASSWORD}", -        "applicationUser" : "${APPLICATION_USER}", -        "applicationPassword" : "${APPLICATION_PASSWORD}", -        "keyBase" : "${SECRET_KEY_BASE}" +        "database-user" : "${DATABASE_USER}", +        "database-password" : "${DATABASE_PASSWORD}", +        "application-user" : "${APPLICATION_USER}", +        "application-password" : "${APPLICATION_PASSWORD}", +        "keybase" : "${SECRET_KEY_BASE}"        }      },      { @@ -104,8 +104,8 @@              },              "env": [                { -                  "name": "RUBYGEM_MIRROR", -                  "value": "${RUBYGEM_MIRROR}" +                "name": "RUBYGEM_MIRROR", +                "value": "${RUBYGEM_MIRROR}"                }              ]            } @@ -148,7 +148,7 @@          "strategy": {            "type": "Recreate",            "recreateParams": { -          "pre": { +            "pre": {                "failurePolicy": "Abort",                "execNewPod": {                  "command": [ @@ -224,7 +224,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databaseUser" +                        "key" : "database-user"                        }                      }                    }, @@ -233,7 +233,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "databasePassword" +                        "key" : "database-password"                        }                      }                    }, @@ -246,7 +246,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "keyBase" +                        "key" : "keybase"                        }                      }                    }, @@ -267,7 +267,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "applicationUser" +                        "key" : "application-user"                        }                      }                    }, @@ -276,7 +276,7 @@                      "valueFrom": {                        "secretKeyRef" : {                          "name" : "${NAME}", -                        "key" : "applicationPassword" +                        "key" : "application-password"                        }                      }                    }, @@ -286,9 +286,9 @@                    }                  ],                  "resources": { -		      "limits": { -			  "memory": "${MEMORY_LIMIT}" -		      } +                  "limits": { +                    "memory": "${MEMORY_LIMIT}" +                  }                  }                }              ] @@ -400,11 +400,21 @@                  "env": [                    {                      "name": "POSTGRESQL_USER", -                    "value": "${DATABASE_USER}" +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-user" +                      } +                    }                    },                    {                      "name": "POSTGRESQL_PASSWORD", -                    "value": "${DATABASE_PASSWORD}" +                    "valueFrom": { +                      "secretKeyRef" : { +                        "name" : "${NAME}", +                        "key" : "database-password" +                      } +                    }                    },                    {                      "name": "POSTGRESQL_DATABASE", @@ -420,9 +430,9 @@                    }                  ],                  "resources": { -		      "limits": { -			  "memory": "${MEMORY_POSTGRESQL_LIMIT}" -		      } +                  "limits": { +                    "memory": "${MEMORY_POSTGRESQL_LIMIT}" +                  }                  }                }              ] diff --git a/roles/openshift_facts/library/openshift_facts.py b/roles/openshift_facts/library/openshift_facts.py index 7c61da950..ec2942b69 100755 --- a/roles/openshift_facts/library/openshift_facts.py +++ b/roles/openshift_facts/library/openshift_facts.py @@ -1382,7 +1382,8 @@ def get_container_openshift_version(facts):                      tag = line[len("IMAGE_VERSION="):].strip()                      # Remove leading "v" and any trailing release info, we just want                      # a version number here: -                    version = tag[1:].split("-")[0] +                    no_v_version = tag[1:] if tag[0] == 'v' else tag +                    version = no_v_version.split("-")[0]                      return version      return None @@ -2027,7 +2028,7 @@ class OpenShiftFacts(object):          if 'docker' in roles:              docker = dict(disable_push_dockerhub=False, -                          options='--log-driver=json-file --log-opt max-size=50m') +                          options='--log-driver=journald')              # NOTE: This is a workaround for a dnf output racecondition that can occur in              # some situations. See https://bugzilla.redhat.com/show_bug.cgi?id=918184              if self.system_facts['ansible_pkg_mgr'] == 'dnf': diff --git a/roles/openshift_hosted_templates/files/v1.4/enterprise/registry-console.yaml b/roles/openshift_hosted_templates/files/v1.4/enterprise/registry-console.yaml index 11478263c..0e3d006a7 100644 --- a/roles/openshift_hosted_templates/files/v1.4/enterprise/registry-console.yaml +++ b/roles/openshift_hosted_templates/files/v1.4/enterprise/registry-console.yaml @@ -103,9 +103,9 @@ parameters:    - description: 'Specify "registry/repository" prefix for container image; e.g. for "registry.access.redhat.com/openshift3/registry-console:latest", set prefix "registry.access.redhat.com/openshift3/"'      name: IMAGE_PREFIX      value: "registry.access.redhat.com/openshift3/" -  - description: 'Specify image version; e.g. for "registry.access.redhat.com/openshift3/registry-console:3.3", set version "3.3"' +  - description: 'Specify image version; e.g. for "registry.access.redhat.com/openshift3/registry-console:3.4", set version "3.4"'      name: IMAGE_VERSION -    value: "3.3" +    value: "3.4"    - description: "The public URL for the Openshift OAuth Provider, e.g. https://openshift.example.com:8443"      name: OPENSHIFT_OAUTH_PROVIDER_URL      required: true diff --git a/roles/openshift_logging/README.md b/roles/openshift_logging/README.md index 856cfa2b9..8651e06e7 100644 --- a/roles/openshift_logging/README.md +++ b/roles/openshift_logging/README.md @@ -36,6 +36,7 @@ When both `openshift_logging_install_logging` and `openshift_logging_upgrade_log  - `openshift_logging_curator_cpu_limit`: The amount of CPU to allocate to Curator. Default is '100m'.  - `openshift_logging_curator_memory_limit`: The amount of memory to allocate to Curator. Unset if not specified.  - `openshift_logging_curator_nodeselector`: A map of labels (e.g. {"node":"infra","region":"west"} to select the nodes where the curator pod will land. +- `openshift_logging_image_pull_secret`: The name of an existing pull secret to link to the logging service accounts  - `openshift_logging_kibana_hostname`: The Kibana hostname. Defaults to 'kibana.example.com'.  - `openshift_logging_kibana_cpu_limit`: The amount of CPU to allocate to Kibana or unset if not specified. diff --git a/roles/openshift_logging/tasks/generate_routes.yaml b/roles/openshift_logging/tasks/generate_routes.yaml index 25877ebff..60694f67e 100644 --- a/roles/openshift_logging/tasks/generate_routes.yaml +++ b/roles/openshift_logging/tasks/generate_routes.yaml @@ -17,5 +17,5 @@      - {name: logging-kibana-ops, host: "{{openshift_logging_kibana_ops_hostname}}"}    loop_control:      loop_var: route_info -  when: (route_info.name == 'logging-kibana-ops' and openshift_logging_use_ops) or route_info.name == 'logging-kibana' +  when: (route_info.name == 'logging-kibana-ops' and openshift_logging_use_ops | bool) or route_info.name == 'logging-kibana'    changed_when: no diff --git a/roles/openshift_logging/tasks/generate_services.yaml b/roles/openshift_logging/tasks/generate_services.yaml index 8eaac76c4..5091c1209 100644 --- a/roles/openshift_logging/tasks/generate_services.yaml +++ b/roles/openshift_logging/tasks/generate_services.yaml @@ -52,7 +52,7 @@      selector:        provider: openshift        component: es-ops -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool    check_mode: no    changed_when: no @@ -67,7 +67,7 @@      selector:        provider: openshift        component: es-ops -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool    check_mode: no    changed_when: no @@ -82,6 +82,6 @@      selector:        provider: openshift        component: kibana-ops -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool    check_mode: no    changed_when: no diff --git a/roles/openshift_logging/tasks/install_curator.yaml b/roles/openshift_logging/tasks/install_curator.yaml index fcfce4e1e..5b474ff39 100644 --- a/roles/openshift_logging/tasks/install_curator.yaml +++ b/roles/openshift_logging/tasks/install_curator.yaml @@ -15,7 +15,7 @@    register: curator_ops_replica_count    when:      - not ansible_check_mode -    - openshift_logging_use_ops +    - openshift_logging_use_ops | bool    ignore_errors: yes    changed_when: no @@ -48,6 +48,6 @@      curator_memory_limit: "{{openshift_logging_curator_ops_memory_limit }}"      replicas: "{{curator_ops_replica_count.stdout | default (0)}}"      curator_node_selector: "{{openshift_logging_curator_ops_nodeselector | default({}) }}" -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool    check_mode: no    changed_when: no diff --git a/roles/openshift_logging/tasks/install_elasticsearch.yaml b/roles/openshift_logging/tasks/install_elasticsearch.yaml index 64e983557..1d6e55e44 100644 --- a/roles/openshift_logging/tasks/install_elasticsearch.yaml +++ b/roles/openshift_logging/tasks/install_elasticsearch.yaml @@ -56,7 +56,7 @@      es_dcs: "{{openshift_logging_facts.elasticsearch_ops.deploymentconfigs}}"      cluster_size: "{{openshift_logging_es_ops_cluster_size}}"    when: -    - openshift_logging_use_ops +    - openshift_logging_use_ops | bool      - "{{es_dcs | length - openshift_logging_es_ops_cluster_size | abs > 1}}"    check_mode: no @@ -71,7 +71,7 @@      openshift_logging_es_pvc_dynamic: "{{openshift_logging_es_ops_pvc_dynamic}}"      openshift_logging_es_pv_selector: "{{openshift_logging_es_ops_pv_selector}}"    when: -    - openshift_logging_use_ops +    - openshift_logging_use_ops | bool    check_mode: no  - name: Init pool of DeploymentConfig names for Elasticsearch Ops @@ -80,7 +80,7 @@    loop_control:      loop_var: deploy_name    when: -    - openshift_logging_use_ops +    - openshift_logging_use_ops | bool  - name: Create new DeploymentConfig names for Elasticsearch Ops    set_fact: es_ops_dc_pool={{es_ops_dc_pool | default([]) + [deploy_name]}} @@ -92,7 +92,7 @@      cluster_size: "{{openshift_logging_es_ops_cluster_size}}"    with_sequence: count={{ openshift_logging_es_ops_cluster_size | int - openshift_logging_current_es_ops_size | int }}    when: -    - openshift_logging_use_ops +    - openshift_logging_use_ops | bool    check_mode: no  - name: Generate Elasticsearch DeploymentConfig for Ops @@ -116,6 +116,6 @@    with_indexed_items:      - "{{ es_ops_dc_pool | default([]) }}"    when: -    - openshift_logging_use_ops +    - openshift_logging_use_ops | bool    check_mode: no    changed_when: no diff --git a/roles/openshift_logging/tasks/install_kibana.yaml b/roles/openshift_logging/tasks/install_kibana.yaml index f4df7de0c..3aeff2cac 100644 --- a/roles/openshift_logging/tasks/install_kibana.yaml +++ b/roles/openshift_logging/tasks/install_kibana.yaml @@ -15,7 +15,7 @@    register: kibana_ops_replica_count    when:      - not ansible_check_mode -    - openshift_logging_use_ops +    - openshift_logging_use_ops | bool    ignore_errors: yes    changed_when: no @@ -55,6 +55,6 @@      kibana_proxy_memory_limit: "{{openshift_logging_kibana_ops_proxy_memory_limit }}"      replicas: "{{kibana_ops_replica_count.stdout | default (0)}}"      kibana_node_selector: "{{openshift_logging_kibana_ops_nodeselector | default({}) }}" -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool    check_mode: no    changed_when: no diff --git a/roles/openshift_logging/tasks/install_logging.yaml b/roles/openshift_logging/tasks/install_logging.yaml index 00c79ee5e..d52429f03 100644 --- a/roles/openshift_logging/tasks/install_logging.yaml +++ b/roles/openshift_logging/tasks/install_logging.yaml @@ -57,6 +57,28 @@      loop_var: file    when: ansible_check_mode +  # TODO replace task with oc_secret module that supports +  # linking when available +- name: Link Pull Secrets With Service Accounts +  include: oc_secret.yaml +  vars: +    kubeconfig: "{{ mktemp.stdout }}/admin.kubeconfig" +    subcommand: link +    service_account: "{{sa_account}}" +    secret_name: "{{openshift_logging_image_pull_secret}}" +    add_args: "--for=pull" +  with_items: +    - default +    - aggregated-logging-elasticsearch +    - aggregated-logging-kibana +    - aggregated-logging-fluentd +    - aggregated-logging-curator +  register: link_pull_secret +  loop_control: +    loop_var: sa_account +  when: openshift_logging_image_pull_secret is defined +  failed_when: link_pull_secret.rc != 0 +  - name: Scaling up cluster    include: start_cluster.yaml    when: start_cluster | default(true) | bool diff --git a/roles/openshift_logging/tasks/main.yaml b/roles/openshift_logging/tasks/main.yaml index 36fb827c3..4c718805e 100644 --- a/roles/openshift_logging/tasks/main.yaml +++ b/roles/openshift_logging/tasks/main.yaml @@ -12,10 +12,6 @@  - debug: msg="Created temp dir {{mktemp.stdout}}" -- name: Ensuring ruamel.yaml package is on target -  command: yum install -y ruamel.yaml -  check_mode: no -  - name: Copy the admin client config(s)    command: >      cp {{ openshift_master_config_dir }}/admin.kubeconfig {{ mktemp.stdout }}/admin.kubeconfig diff --git a/roles/openshift_logging/tasks/oc_apply.yaml b/roles/openshift_logging/tasks/oc_apply.yaml index c362b7fca..cb9509de1 100644 --- a/roles/openshift_logging/tasks/oc_apply.yaml +++ b/roles/openshift_logging/tasks/oc_apply.yaml @@ -1,12 +1,13 @@  ---  - name: Checking generation of {{file_content.kind}} {{file_content.metadata.name}} -  shell: > +  command: >      {{ openshift.common.client_binary }}      --config={{ kubeconfig }}      get {{file_content.kind}} {{file_content.metadata.name}}      -o jsonpath='{.metadata.resourceVersion}' -    -n {{namespace}} || echo 0 +    -n {{namespace}}    register: generation_init +  failed_when: "'not found' not in generation_init.stderr and generation_init.stdout == ''"    changed_when: no  - name: Applying {{file_name}} @@ -19,11 +20,33 @@    changed_when: no  - name: Determine change status of {{file_content.kind}} {{file_content.metadata.name}} -  shell: > +  command: >      {{ openshift.common.client_binary }} --config={{ kubeconfig }}      get {{file_content.kind}} {{file_content.metadata.name}}      -o jsonpath='{.metadata.resourceVersion}' -    -n {{namespace}} || echo 0 +    -n {{namespace}}    register: generation_changed -  failed_when: "'error' in generation_changed.stderr" -  changed_when: generation_changed.stdout | int  > generation_init.stdout | int +  failed_when: "'not found' not in generation_changed.stderr and generation_changed.stdout == ''" +  changed_when: generation_changed.stdout | default (0) | int  > generation_init.stdout | default(0) | int +  when: +    - "'field is immutable' not in generation_apply.stderr" + +- name: Removing previous {{file_name}} +  command: > +    {{ openshift.common.client_binary }} --config={{ kubeconfig }} +    delete -f {{ file_name }} +    -n {{ namespace }} +  register: generation_delete +  failed_when: "'error' in generation_delete.stderr" +  changed_when: generation_delete.rc == 0 +  when: "'field is immutable' in generation_apply.stderr" + +- name: Recreating {{file_name}} +  command: > +    {{ openshift.common.client_binary }} --config={{ kubeconfig }} +    apply -f {{ file_name }} +    -n {{ namespace }} +  register: generation_apply +  failed_when: "'error' in generation_apply.stderr" +  changed_when: generation_apply.rc == 0 +  when: "'field is immutable' in generation_apply.stderr" diff --git a/roles/openshift_logging/tasks/oc_secret.yaml b/roles/openshift_logging/tasks/oc_secret.yaml new file mode 100644 index 000000000..de37e4f6d --- /dev/null +++ b/roles/openshift_logging/tasks/oc_secret.yaml @@ -0,0 +1,7 @@ +--- +- command: > +    {{ openshift.common.client_binary }} +    --config={{ kubeconfig }} +    secret {{subcommand}} {{service_account}} {{secret_name}} +    {{add_args}} +    -n {{openshift_logging_namespace}} diff --git a/roles/openshift_logging/tasks/start_cluster.yaml b/roles/openshift_logging/tasks/start_cluster.yaml index 07489ae79..69d2b2b6b 100644 --- a/roles/openshift_logging/tasks/start_cluster.yaml +++ b/roles/openshift_logging/tasks/start_cluster.yaml @@ -86,7 +86,7 @@    with_items: "{{es_dc.stdout_lines}}"    loop_control:      loop_var: object -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool  - command: >      {{openshift.common.client_binary}} --config={{mktemp.stdout}}/admin.kubeconfig get dc -l component=kibana-ops -o name -n {{openshift_logging_namespace}} @@ -104,7 +104,7 @@    with_items: "{{kibana_dc.stdout_lines}}"    loop_control:      loop_var: object -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool  - command: >      {{openshift.common.client_binary}} --config={{mktemp.stdout}}/admin.kubeconfig get dc -l component=curator-ops -o name -n {{openshift_logging_namespace}} @@ -122,4 +122,4 @@    with_items: "{{curator_dc.stdout_lines}}"    loop_control:      loop_var: object -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool diff --git a/roles/openshift_logging/tasks/stop_cluster.yaml b/roles/openshift_logging/tasks/stop_cluster.yaml index 8e0df8344..7826efabe 100644 --- a/roles/openshift_logging/tasks/stop_cluster.yaml +++ b/roles/openshift_logging/tasks/stop_cluster.yaml @@ -81,7 +81,7 @@    with_items: "{{es_dc.stdout_lines}}"    loop_control:      loop_var: object -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool  - command: >      {{openshift.common.client_binary}} --config={{mktemp.stdout}}/admin.kubeconfig get dc -l component=kibana-ops -o name -n {{openshift_logging_namespace}} @@ -98,7 +98,7 @@    with_items: "{{kibana_dc.stdout_lines}}"    loop_control:      loop_var: object -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool  - command: >      {{openshift.common.client_binary}} --config={{mktemp.stdout}}/admin.kubeconfig get dc -l component=curator-ops -o name -n {{openshift_logging_namespace}} @@ -115,4 +115,4 @@    with_items: "{{curator_dc.stdout_lines}}"    loop_control:      loop_var: object -  when: openshift_logging_use_ops +  when: openshift_logging_use_ops | bool diff --git a/roles/openshift_master/templates/master.yaml.v1.j2 b/roles/openshift_master/templates/master.yaml.v1.j2 index cf7ceacff..9ae54dac1 100644 --- a/roles/openshift_master/templates/master.yaml.v1.j2 +++ b/roles/openshift_master/templates/master.yaml.v1.j2 @@ -102,7 +102,11 @@ imagePolicyConfig:{{ openshift.master.image_policy_config | to_padded_yaml(level  kind: MasterConfig  kubeletClientInfo:  {# TODO: allow user specified kubelet port #} +{% if openshift.common.version_gte_3_2_or_1_2 | bool %} +  ca: ca-bundle.crt +{% else %}    ca: ca.crt +{% endif %}    certFile: master.kubelet-client.crt    keyFile: master.kubelet-client.key    port: 10250 @@ -221,7 +225,11 @@ servingInfo:    bindAddress: {{ openshift.master.bind_addr }}:{{ openshift.master.api_port }}    bindNetwork: tcp4    certFile: master.server.crt +{% if openshift.common.version_gte_3_2_or_1_2 | bool %} +  clientCA: ca-bundle.crt +{% else %}    clientCA: ca.crt +{% endif %}    keyFile: master.server.key    maxRequestsInFlight: {{ openshift.master.max_requests_inflight }}    requestTimeoutSeconds: 3600 diff --git a/roles/openshift_master_certificates/tasks/main.yml b/roles/openshift_master_certificates/tasks/main.yml index 4620dd877..7a5ed51ec 100644 --- a/roles/openshift_master_certificates/tasks/main.yml +++ b/roles/openshift_master_certificates/tasks/main.yml @@ -38,12 +38,22 @@    when: master_certs_missing | bool and inventory_hostname != openshift_ca_host    delegate_to: "{{ openshift_ca_host }}" +- find: +    paths: "{{ openshift_master_config_dir }}/legacy-ca/" +    patterns: ".*-ca.crt" +    use_regex: true +  register: g_master_legacy_ca_result +  delegate_to: "{{ openshift_ca_host }}" +  - name: Create the master server certificate    command: >      {{ hostvars[openshift_ca_host].openshift.common.client_binary }} adm ca create-server-cert      {% for named_ca_certificate in openshift.master.named_certificates | default([]) | oo_collect('cafile') %}      --certificate-authority {{ named_ca_certificate }}      {% endfor %} +    {% for legacy_ca_certificate in g_master_legacy_ca_result.files | default([]) | oo_collect('path') %} +    --certificate-authority {{ legacy_ca_certificate }} +    {% endfor %}      --hostnames={{ hostvars[item].openshift.common.all_hostnames | join(',') }}      --cert={{ openshift_generated_configs_dir }}/master-{{ hostvars[item].openshift.common.hostname }}/master.server.crt      --key={{ openshift_generated_configs_dir }}/master-{{ hostvars[item].openshift.common.hostname }}/master.server.key diff --git a/roles/openshift_metrics/defaults/main.yaml b/roles/openshift_metrics/defaults/main.yaml index b99adf779..dd5a20d5b 100644 --- a/roles/openshift_metrics/defaults/main.yaml +++ b/roles/openshift_metrics/defaults/main.yaml @@ -13,6 +13,7 @@ openshift_metrics_hawkular_requests_cpu: null  openshift_metrics_hawkular_cert: ""  openshift_metrics_hawkular_key: ""  openshift_metrics_hawkular_ca: "" +openshift_metrics_hawkular_nodeselector: ""  openshift_metrics_cassandra_replicas: 1  openshift_metrics_cassandra_storage_type: emptydir @@ -21,12 +22,14 @@ openshift_metrics_cassandra_limits_memory: 2G  openshift_metrics_cassandra_limits_cpu: null  openshift_metrics_cassandra_requests_memory: 1G  openshift_metrics_cassandra_requests_cpu: null +openshift_metrics_cassandra_nodeselector: ""  openshift_metrics_heapster_standalone: False  openshift_metrics_heapster_limits_memory: 3.75G  openshift_metrics_heapster_limits_cpu: null  openshift_metrics_heapster_requests_memory: 0.9375G  openshift_metrics_heapster_requests_cpu: null +openshift_metrics_heapster_nodeselector: ""  openshift_metrics_duration: 7  openshift_metrics_resolution: 15s diff --git a/roles/openshift_metrics/tasks/install_cassandra.yaml b/roles/openshift_metrics/tasks/install_cassandra.yaml index a9340acc3..9e25071af 100644 --- a/roles/openshift_metrics/tasks/install_cassandra.yaml +++ b/roles/openshift_metrics/tasks/install_cassandra.yaml @@ -18,6 +18,7 @@      node: "{{ item }}"      master: "{{ (item == '1')|string|lower }}"      replica_count: "{{cassandra_replica_count.results[item|int - 1].stdout}}" +    node_selector: "{{openshift_metrics_cassandra_nodeselector | default('') }}"    with_sequence: count={{ openshift_metrics_cassandra_replicas }}    changed_when: false diff --git a/roles/openshift_metrics/tasks/install_hawkular.yaml b/roles/openshift_metrics/tasks/install_hawkular.yaml index 00f7b2554..1ba11efa8 100644 --- a/roles/openshift_metrics/tasks/install_hawkular.yaml +++ b/roles/openshift_metrics/tasks/install_hawkular.yaml @@ -13,6 +13,7 @@      dest: "{{ mktemp.stdout }}/templates/hawkular_metrics_rc.yaml"    vars:      replica_count: "{{hawkular_metrics_replica_count.stdout | default(0)}}" +    node_selector: "{{openshift_metrics_hawkular_nodeselector | default('') }}"    changed_when: false  - name: read hawkular-metrics route destination ca certificate diff --git a/roles/openshift_metrics/tasks/install_heapster.yaml b/roles/openshift_metrics/tasks/install_heapster.yaml index 39df797ab..44bab8ace 100644 --- a/roles/openshift_metrics/tasks/install_heapster.yaml +++ b/roles/openshift_metrics/tasks/install_heapster.yaml @@ -11,4 +11,5 @@    template: src=heapster.j2 dest={{mktemp.stdout}}/templates/metrics-heapster-rc.yaml    vars:      replica_count: "{{heapster_replica_count.stdout | default(0)}}" +    node_selector: "{{openshift_metrics_heapster_nodeselector | default('') }}"    changed_when: no diff --git a/roles/openshift_metrics/tasks/install_support.yaml b/roles/openshift_metrics/tasks/install_support.yaml index cc5acc6e5..5cefb273d 100644 --- a/roles/openshift_metrics/tasks/install_support.yaml +++ b/roles/openshift_metrics/tasks/install_support.yaml @@ -9,7 +9,7 @@    when: htpasswd_check.rc  == 1  - name: Check control node to see if keytool is installed -  local_action: command which htpasswd +  local_action: command which keytool    register: keytool_check    failed_when: no    changed_when: no diff --git a/roles/openshift_metrics/templates/hawkular_cassandra_rc.j2 b/roles/openshift_metrics/templates/hawkular_cassandra_rc.j2 index abd4ff939..876cb1915 100644 --- a/roles/openshift_metrics/templates/hawkular_cassandra_rc.j2 +++ b/roles/openshift_metrics/templates/hawkular_cassandra_rc.j2 @@ -19,6 +19,12 @@ spec:          type: hawkular-cassandra      spec:        serviceAccount: cassandra +{% if node_selector is iterable and node_selector | length > 0 %} +      nodeSelector: +{% for key, value in node_selector.iteritems() %} +        {{key}}: "{{value}}" +{% endfor %} +{% endif %}        containers:        - image: "{{ openshift_metrics_image_prefix }}metrics-cassandra:{{ openshift_metrics_image_version }}"          name: hawkular-cassandra-{{ node }} diff --git a/roles/openshift_metrics/templates/hawkular_metrics_rc.j2 b/roles/openshift_metrics/templates/hawkular_metrics_rc.j2 index f78621674..d39f1b43a 100644 --- a/roles/openshift_metrics/templates/hawkular_metrics_rc.j2 +++ b/roles/openshift_metrics/templates/hawkular_metrics_rc.j2 @@ -17,6 +17,12 @@ spec:          name: hawkular-metrics      spec:        serviceAccount: hawkular +{% if node_selector is iterable and node_selector | length > 0 %} +      nodeSelector: +{% for key, value in node_selector.iteritems() %} +        {{key}}: "{{value}}" +{% endfor %} +{% endif %}        containers:        - image: {{openshift_metrics_image_prefix}}metrics-hawkular-metrics:{{openshift_metrics_image_version}}          name: hawkular-metrics diff --git a/roles/openshift_metrics/templates/heapster.j2 b/roles/openshift_metrics/templates/heapster.j2 index eeca03be0..7c837db4d 100644 --- a/roles/openshift_metrics/templates/heapster.j2 +++ b/roles/openshift_metrics/templates/heapster.j2 @@ -18,6 +18,12 @@ spec:          name: heapster      spec:        serviceAccountName: heapster +{% if node_selector is iterable and node_selector | length > 0 %} +      nodeSelector: +{% for key, value in node_selector.iteritems() %} +        {{key}}: "{{value}}" +{% endfor %} +{% endif %}        containers:        - name: heapster          image: {{openshift_metrics_image_prefix}}metrics-heapster:{{openshift_metrics_image_version}} diff --git a/roles/openshift_node_certificates/handlers/main.yml b/roles/openshift_node_certificates/handlers/main.yml index a74668b13..1aa826c09 100644 --- a/roles/openshift_node_certificates/handlers/main.yml +++ b/roles/openshift_node_certificates/handlers/main.yml @@ -8,3 +8,4 @@    systemd:      name: docker      state: restarted +  when: not openshift_certificates_redeploy | default(false) | bool diff --git a/roles/openshift_node_certificates/tasks/main.yml b/roles/openshift_node_certificates/tasks/main.yml index a263f4f3a..4cb89aba2 100644 --- a/roles/openshift_node_certificates/tasks/main.yml +++ b/roles/openshift_node_certificates/tasks/main.yml @@ -42,20 +42,30 @@    when: node_certs_missing | bool    delegate_to: "{{ openshift_ca_host }}" +- find: +    paths: "{{ openshift.common.config_base }}/master/legacy-ca/" +    patterns: ".*-ca.crt" +    use_regex: true +  register: g_master_legacy_ca_result +  delegate_to: "{{ openshift_ca_host }}" +  - name: Generate the node client config    command: >      {{ hostvars[openshift_ca_host].openshift.common.client_binary }} adm create-api-client-config -      {% for named_ca_certificate in hostvars[openshift_ca_host].openshift.master.named_certificates | default([]) | oo_collect('cafile') %} -      --certificate-authority {{ named_ca_certificate }} -      {% endfor %} -      --certificate-authority={{ openshift_ca_cert }} -      --client-dir={{ openshift_generated_configs_dir }}/node-{{ hostvars[item].openshift.common.hostname }} -      --groups=system:nodes -      --master={{ hostvars[openshift_ca_host].openshift.master.api_url }} -      --signer-cert={{ openshift_ca_cert }} -      --signer-key={{ openshift_ca_key }} -      --signer-serial={{ openshift_ca_serial }} -      --user=system:node:{{ hostvars[item].openshift.common.hostname }} +    {% for named_ca_certificate in hostvars[openshift_ca_host].openshift.master.named_certificates | default([]) | oo_collect('cafile') %} +    --certificate-authority {{ named_ca_certificate }} +    {% endfor %} +    {% for legacy_ca_certificate in g_master_legacy_ca_result.files | default([]) | oo_collect('path') %} +    --certificate-authority {{ legacy_ca_certificate }} +    {% endfor %} +    --certificate-authority={{ openshift_ca_cert }} +    --client-dir={{ openshift_generated_configs_dir }}/node-{{ hostvars[item].openshift.common.hostname }} +    --groups=system:nodes +    --master={{ hostvars[openshift_ca_host].openshift.master.api_url }} +    --signer-cert={{ openshift_ca_cert }} +    --signer-key={{ openshift_ca_key }} +    --signer-serial={{ openshift_ca_serial }} +    --user=system:node:{{ hostvars[item].openshift.common.hostname }}    args:      creates: "{{ openshift_generated_configs_dir }}/node-{{ hostvars[item].openshift.common.hostname }}"    with_items: "{{ hostvars | 
