diff options
90 files changed, 3838 insertions, 2326 deletions
diff --git a/.tito/packages/openshift-ansible b/.tito/packages/openshift-ansible index eab5e9469..9a8ce8a69 100644 --- a/.tito/packages/openshift-ansible +++ b/.tito/packages/openshift-ansible @@ -1 +1 @@ -3.6.10-1 ./ +3.6.12-1 ./ diff --git a/Dockerfile.rhel7 b/Dockerfile.rhel7 index f3d45837a..0d5a6038a 100644 --- a/Dockerfile.rhel7 +++ b/Dockerfile.rhel7 @@ -1,26 +1,41 @@ -FROM rhel7 +FROM openshift3/playbook2image -MAINTAINER Troy Dawson <tdawson@redhat.com> +MAINTAINER OpenShift Team <dev@lists.openshift.redhat.com> -LABEL Name="openshift3/installer" -LABEL Vendor="Red Hat" License=GPLv2+ -LABEL Version="v3.1.1.901" -LABEL Release="6" -LABEL BZComponent="aos3-installation-docker" -LABEL Architecture="x86_64" -LABEL io.k8s.description="Ansible code and playbooks for installing Openshift Container Platform." \ - io.k8s.display-name="Openshift Installer" \ - io.openshift.tags="openshift,installer" +LABEL name="openshift3/openshift-ansible" \ + summary="OpenShift's installation and configuration tool" \ + description="A containerized openshift-ansible image to let you run playbooks to install, upgrade, maintain and check an OpenShift cluster" \ + url="https://github.com/openshift/openshift-ansible" \ + io.k8s.display-name="openshift-ansible" \ + io.k8s.description="A containerized openshift-ansible image to let you run playbooks to install, upgrade, maintain and check an OpenShift cluster" \ + io.openshift.expose-services="" \ + io.openshift.tags="openshift,install,upgrade,ansible" \ + com.redhat.component="aos3-installation-docker" \ + version="v3.4.1" \ + release="1" \ + architecture="x86_64" -RUN INSTALL_PKGS="atomic-openshift-utils" && \ - yum install -y --enablerepo=rhel-7-server-ose-3.2-rpms $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ +# Playbooks, roles and their dependencies are installed from packages. +# Unlike in Dockerfile, we don't invoke the 'assemble' script here +# because all content and dependencies (like 'oc') is already +# installed via yum. +USER root +RUN INSTALL_PKGS="atomic-openshift-utils atomic-openshift-clients" && \ + yum repolist > /dev/null && \ + yum-config-manager --enable rhel-7-server-ose-3.4-rpms && \ + yum install -y $INSTALL_PKGS && \ yum clean all -# Expect user to mount a workdir for container output (installer.cfg, hosts inventory, ansible log) -VOLUME /var/lib/openshift-installer/ -WORKDIR /var/lib/openshift-installer/ +USER ${USER_UID} -RUN mkdir -p /var/lib/openshift-installer/ +# The playbook to be run is specified via the PLAYBOOK_FILE env var. +# This sets a default of openshift_facts.yml as it's an informative playbook +# that can help test that everything is set properly (inventory, sshkeys). +# As the playbooks are installed via packages instead of being copied to +# $APP_HOME by the 'assemble' script, we set the WORK_DIR env var to the +# location of openshift-ansible. +ENV PLAYBOOK_FILE=playbooks/byo/openshift_facts.yml \ + WORK_DIR=/usr/share/ansible/openshift-ansible \ + OPTS="-v" -ENTRYPOINT ["/usr/bin/atomic-openshift-installer", "-c", "/var/lib/openshift-installer/installer.cfg", "--ansible-log-path", "/var/lib/openshift-installer/ansible.log"] +CMD [ "/usr/libexec/s2i/run" ] diff --git a/inventory/byo/hosts.openstack b/inventory/byo/hosts.openstack index ea7e905cb..c648078c4 100644 --- a/inventory/byo/hosts.openstack +++ b/inventory/byo/hosts.openstack @@ -15,7 +15,7 @@ ansible_become=yes # Debug level for all OpenShift components (Defaults to 2) debug_level=2 -deployment_type=openshift-enterprise +openshift_deployment_type=openshift-enterprise openshift_additional_repos=[{'id': 'ose-3.1', 'name': 'ose-3.1', 'baseurl': 'http://pulp.dist.prod.ext.phx2.redhat.com/content/dist/rhel/server/7/7Server/x86_64/ose/3.1/os', 'enabled': 1, 'gpgcheck': 0}] diff --git a/inventory/byo/hosts.origin.example b/inventory/byo/hosts.origin.example index 033ce8a82..d61f033f8 100644 --- a/inventory/byo/hosts.origin.example +++ b/inventory/byo/hosts.origin.example @@ -23,7 +23,7 @@ ansible_ssh_user=root debug_level=2 # deployment type valid values are origin, online, atomic-enterprise and openshift-enterprise -deployment_type=origin +openshift_deployment_type=origin # Specify the generic release of OpenShift to install. This is used mainly just during installation, after which we # rely on the version running on the first master. Works best for containerized installs where we can usually @@ -91,6 +91,10 @@ openshift_release=v1.5 # Specify exact version of etcd to configure or upgrade to. # etcd_version="3.1.0" +# Enable etcd debug logging, defaults to false +# etcd_debug=true +# Set etcd log levels by package +# etcd_log_package_levels="etcdserver=WARNING,security=DEBUG" # Upgrade Hooks # @@ -384,6 +388,9 @@ openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true', # based on the number of nodes matching the openshift registry selector. #openshift_hosted_registry_replicas=2 # +# Validity of the auto-generated certificate in days (optional) +#openshift_hosted_registry_cert_expire_days=730 +# # Disable management of the OpenShift Registry #openshift_hosted_manage_registry=false @@ -750,6 +757,13 @@ openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true', # by deployment_type=origin #openshift_enable_origin_repo=false +# Validity of the auto-generated certificates in days. +# See also openshift_hosted_registry_cert_expire_days above. +# +#openshift_ca_cert_expire_days=1825 +#openshift_node_cert_expire_days=730 +#openshift_master_cert_expire_days=730 + # host group for masters [masters] ose3-master[1:3]-ansible.test.example.com diff --git a/inventory/byo/hosts.ose.example b/inventory/byo/hosts.ose.example index 49bcb7405..823d6f58f 100644 --- a/inventory/byo/hosts.ose.example +++ b/inventory/byo/hosts.ose.example @@ -23,7 +23,7 @@ ansible_ssh_user=root debug_level=2 # deployment type valid values are origin, online, atomic-enterprise, and openshift-enterprise -deployment_type=openshift-enterprise +openshift_deployment_type=openshift-enterprise # Specify the generic release of OpenShift to install. This is used mainly just during installation, after which we # rely on the version running on the first master. Works best for containerized installs where we can usually @@ -91,6 +91,10 @@ openshift_release=v3.5 # Specify exact version of etcd to configure or upgrade to. # etcd_version="3.1.0" +# Enable etcd debug logging, defaults to false +# etcd_debug=true +# Set etcd log levels by package +# etcd_log_package_levels="etcdserver=WARNING,security=DEBUG" # Upgrade Hooks # @@ -384,6 +388,9 @@ openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true', # based on the number of nodes matching the openshift registry selector. #openshift_hosted_registry_replicas=2 # +# Validity of the auto-generated certificate in days (optional) +#openshift_hosted_registry_cert_expire_days=730 +# # Disable management of the OpenShift Registry #openshift_hosted_manage_registry=false @@ -747,6 +754,13 @@ openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true', # Enable API service auditing, available as of 3.2 #openshift_master_audit_config={"basicAuditEnabled": true} +# Validity of the auto-generated certificates in days. +# See also openshift_hosted_registry_cert_expire_days above. +# +#openshift_ca_cert_expire_days=1825 +#openshift_node_cert_expire_days=730 +#openshift_master_cert_expire_days=730 + # host group for masters [masters] ose3-master[1:3]-ansible.test.example.com diff --git a/openshift-ansible.spec b/openshift-ansible.spec index 228afea58..c25c152df 100644 --- a/openshift-ansible.spec +++ b/openshift-ansible.spec @@ -9,7 +9,7 @@ %global __requires_exclude ^/usr/bin/ansible-playbook$ Name: openshift-ansible -Version: 3.6.10 +Version: 3.6.12 Release: 1%{?dist} Summary: Openshift and Atomic Enterprise Ansible License: ASL 2.0 @@ -270,6 +270,40 @@ Atomic OpenShift Utilities includes %changelog +* Thu Mar 30 2017 Jenkins CD Merge Bot <tdawson@redhat.com> 3.6.12-1 +- Update example inventory files to mention certificate validity parameters. + (vsemushi@redhat.com) +- openshift_hosted: add openshift_hosted_registry_cert_expire_days parameter. + (vsemushi@redhat.com) +- oc_adm_ca_server_cert.py: re-generate. (vsemushi@redhat.com) +- oc_adm_ca_server_cert: add expire_days parameter. (vsemushi@redhat.com) +- openshift_ca: add openshift_ca_cert_expire_days and + openshift_master_cert_expire_days parameters. (vsemushi@redhat.com) +- redeploy-certificates/registry.yml: add + openshift_hosted_registry_cert_expire_days parameter. (vsemushi@redhat.com) +- openshift_master_certificates: add openshift_master_cert_expire_days + parameter. (vsemushi@redhat.com) +- openshift_node_certificates: add openshift_node_cert_expire_days parameter. + (vsemushi@redhat.com) +- Update Dockerfile.rhel7 to reflect changes to Dockerfile (pep@redhat.com) + +* Wed Mar 29 2017 Jenkins CD Merge Bot <tdawson@redhat.com> 3.6.11-1 +- Add etcd_debug and etcd_log_package_levels variables (sdodson@redhat.com) +- Make the OCP available version detection excluder free (jchaloup@redhat.com) +- Add test scaffold for docker_image_availability.py (rhcarvalho@gmail.com) +- Add unit tests for package_version.py (rhcarvalho@gmail.com) +- Add unit tests for package_update.py (rhcarvalho@gmail.com) +- Add unit tests for package_availability.py (rhcarvalho@gmail.com) +- Add unit tests for mixins.py (rhcarvalho@gmail.com) +- Test recursively finding subclasses (rhcarvalho@gmail.com) +- Test OpenShift health check loader (rhcarvalho@gmail.com) +- Rename module_executor -> execute_module (rhcarvalho@gmail.com) +- Use oo_version_gte_3_6+ for future versions and treat 1.x origin as legacy. + Add tests. (abutcher@redhat.com) +- Added 3.5 -> 3.6 upgrade playbooks (skuznets@redhat.com) +- Add oo_version_gte_X_X_or_Y_Y version comparison filters. + (abutcher@redhat.com) + * Tue Mar 28 2017 Jenkins CD Merge Bot <tdawson@redhat.com> 3.6.10-1 - Use meta/main.yml for role dependencies (rteague@redhat.com) - Upgrade specific rpms instead of just master/node. (dgoodwin@redhat.com) diff --git a/playbooks/byo/openshift-cluster/config.yml b/playbooks/byo/openshift-cluster/config.yml index 86eff4ca4..4db0720d0 100644 --- a/playbooks/byo/openshift-cluster/config.yml +++ b/playbooks/byo/openshift-cluster/config.yml @@ -7,5 +7,4 @@ vars: openshift_cluster_id: "{{ cluster_id | default('default') }}" openshift_debug_level: "{{ debug_level | default(2) }}" - openshift_deployment_type: "{{ deployment_type }}" openshift_deployment_subtype: "{{ deployment_subtype | default(none) }}" diff --git a/playbooks/byo/openshift-cluster/openshift-logging.yml b/playbooks/byo/openshift-cluster/openshift-logging.yml index eebfcd20d..f8eebe898 100644 --- a/playbooks/byo/openshift-cluster/openshift-logging.yml +++ b/playbooks/byo/openshift-cluster/openshift-logging.yml @@ -32,4 +32,3 @@ vars: openshift_cluster_id: "{{ cluster_id | default('default') }}" openshift_debug_level: "{{ debug_level | default(2) }}" - openshift_deployment_type: "{{ deployment_type }}" diff --git a/playbooks/byo/openshift-cluster/upgrades/docker/upgrade.yml b/playbooks/byo/openshift-cluster/upgrades/docker/upgrade.yml index d5fd7c424..5feb33be4 100644 --- a/playbooks/byo/openshift-cluster/upgrades/docker/upgrade.yml +++ b/playbooks/byo/openshift-cluster/upgrades/docker/upgrade.yml @@ -30,7 +30,6 @@ g_new_master_hosts: [] g_new_node_hosts: [] openshift_cluster_id: "{{ cluster_id | default('default') }}" - openshift_deployment_type: "{{ deployment_type }}" - include: ../../../../common/openshift-cluster/upgrades/initialize_nodes_to_upgrade.yml diff --git a/playbooks/byo/openshift-etcd/restart.yml b/playbooks/byo/openshift-etcd/restart.yml index 6713f07e3..19403116d 100644 --- a/playbooks/byo/openshift-etcd/restart.yml +++ b/playbooks/byo/openshift-etcd/restart.yml @@ -4,5 +4,3 @@ - always - include: ../../common/openshift-etcd/restart.yml - vars: - openshift_deployment_type: "{{ deployment_type }}" diff --git a/playbooks/byo/openshift-master/restart.yml b/playbooks/byo/openshift-master/restart.yml index 2d20f69f4..21e4cff1b 100644 --- a/playbooks/byo/openshift-master/restart.yml +++ b/playbooks/byo/openshift-master/restart.yml @@ -4,5 +4,3 @@ - always - include: ../../common/openshift-master/restart.yml - vars: - openshift_deployment_type: "{{ deployment_type }}" diff --git a/playbooks/byo/openshift-master/scaleup.yml b/playbooks/byo/openshift-master/scaleup.yml index 7075bb59e..a5705e990 100644 --- a/playbooks/byo/openshift-master/scaleup.yml +++ b/playbooks/byo/openshift-master/scaleup.yml @@ -27,4 +27,3 @@ vars: openshift_cluster_id: "{{ cluster_id | default('default') }}" openshift_debug_level: "{{ debug_level | default(2) }}" - openshift_deployment_type: "{{ deployment_type }}" diff --git a/playbooks/byo/openshift-node/restart.yml b/playbooks/byo/openshift-node/restart.yml index 3985a83bb..6861625b9 100644 --- a/playbooks/byo/openshift-node/restart.yml +++ b/playbooks/byo/openshift-node/restart.yml @@ -4,5 +4,3 @@ - always - include: ../../common/openshift-node/restart.yml - vars: - openshift_deployment_type: "{{ deployment_type }}" diff --git a/playbooks/byo/openshift-node/scaleup.yml b/playbooks/byo/openshift-node/scaleup.yml index 2b10b6c76..88d236b53 100644 --- a/playbooks/byo/openshift-node/scaleup.yml +++ b/playbooks/byo/openshift-node/scaleup.yml @@ -27,6 +27,5 @@ vars: openshift_cluster_id: "{{ cluster_id | default('default') }}" openshift_debug_level: "{{ debug_level | default(2) }}" - openshift_deployment_type: "{{ deployment_type }}" openshift_master_etcd_hosts: "{{ groups.etcd | default([]) }}" openshift_master_etcd_port: 2379 diff --git a/playbooks/byo/rhel_subscribe.yml b/playbooks/byo/rhel_subscribe.yml index 65c0b1c01..8c6d77024 100644 --- a/playbooks/byo/rhel_subscribe.yml +++ b/playbooks/byo/rhel_subscribe.yml @@ -5,8 +5,6 @@ - name: Subscribe hosts, update repos and update OS packages hosts: l_oo_all_hosts - vars: - openshift_deployment_type: "{{ deployment_type }}" roles: - role: rhel_subscribe when: deployment_type in ['atomic-enterprise', 'enterprise', 'openshift-enterprise'] and diff --git a/playbooks/common/openshift-cluster/config.yml b/playbooks/common/openshift-cluster/config.yml index ff4c4b0d7..1b967b7f1 100644 --- a/playbooks/common/openshift-cluster/config.yml +++ b/playbooks/common/openshift-cluster/config.yml @@ -27,9 +27,6 @@ when: openshift_docker_selinux_enabled is not defined - include: disable_excluder.yml - vars: - # the excluders needs to be disabled no matter what status says - with_status_check: false tags: - always diff --git a/playbooks/common/openshift-cluster/enable_dnsmasq.yml b/playbooks/common/openshift-cluster/enable_dnsmasq.yml index ca5177852..5425f448f 100644 --- a/playbooks/common/openshift-cluster/enable_dnsmasq.yml +++ b/playbooks/common/openshift-cluster/enable_dnsmasq.yml @@ -56,8 +56,6 @@ - role: node local_facts: dns_ip: "{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}" - vars: - openshift_deployment_type: "{{ deployment_type }}" roles: - openshift_node_dnsmasq post_tasks: diff --git a/playbooks/common/openshift-cluster/initialize_facts.yml b/playbooks/common/openshift-cluster/initialize_facts.yml index 18f99728c..9cebecd68 100644 --- a/playbooks/common/openshift-cluster/initialize_facts.yml +++ b/playbooks/common/openshift-cluster/initialize_facts.yml @@ -15,5 +15,3 @@ hostname: "{{ openshift_hostname | default(None) }}" - set_fact: openshift_docker_hosted_registry_network: "{{ hostvars[groups.oo_first_master.0].openshift.common.portal_net }}" - - set_fact: - openshift_deployment_type: "{{ deployment_type }}" diff --git a/playbooks/common/openshift-cluster/initialize_openshift_version.yml b/playbooks/common/openshift-cluster/initialize_openshift_version.yml index 1f74e929f..07b38920f 100644 --- a/playbooks/common/openshift-cluster/initialize_openshift_version.yml +++ b/playbooks/common/openshift-cluster/initialize_openshift_version.yml @@ -18,18 +18,6 @@ msg: Incompatible versions of yum and subscription-manager found. You may need to update yum and yum-utils. when: "not openshift.common.is_atomic | bool and 'Plugin \"search-disabled-repos\" requires API 2.7. Supported API is 2.6.' in yum_ver_test.stdout" -# TODO(jchaloup): find a different way how to make repoquery --qf '%version` atomic-openshift work without disabling the excluders -- include: disable_excluder.yml - vars: - # the excluders needs to be disabled no matter what status says - with_status_check: false - # Only openshift excluder needs to be temporarily disabled - # So ignore the docker one - enable_docker_excluder: false - tags: - - always - when: openshift_upgrade_target is not defined - - name: Determine openshift_version to configure on first master hosts: oo_first_master roles: @@ -44,13 +32,3 @@ openshift_version: "{{ hostvars[groups.oo_first_master.0].openshift_version }}" roles: - openshift_version - - # Re-enable excluders if they are meant to be enabled (and only during installation, upgrade disables the excluders before this play) -- include: reset_excluder.yml - vars: - # Only openshift excluder needs to be re-enabled - # So ignore the docker one - enable_docker_excluder: false - tags: - - always - when: openshift_upgrade_target is not defined diff --git a/playbooks/common/openshift-cluster/redeploy-certificates/registry.yml b/playbooks/common/openshift-cluster/redeploy-certificates/registry.yml index 08ebfe735..e82996cf4 100644 --- a/playbooks/common/openshift-cluster/redeploy-certificates/registry.yml +++ b/playbooks/common/openshift-cluster/redeploy-certificates/registry.yml @@ -69,6 +69,9 @@ --hostnames="{{ docker_registry_service_ip.results.clusterip }},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 + {% if openshift_version | oo_version_gte_3_5_or_1_5(openshift.common.deployment_type) | bool %} + --expire-days={{ openshift_hosted_registry_cert_expire_days | default(730) }} + {% endif %} - name: Update registry certificates secret oc_secret: diff --git a/playbooks/common/openshift-cluster/std_include.yml b/playbooks/common/openshift-cluster/std_include.yml index 078991b12..74cc1d527 100644 --- a/playbooks/common/openshift-cluster/std_include.yml +++ b/playbooks/common/openshift-cluster/std_include.yml @@ -22,8 +22,6 @@ - always tasks: - include_vars: ../../byo/openshift-cluster/cluster_hosts.yml - - set_fact: - openshift_deployment_type: "{{ deployment_type }}" - include: evaluate_groups.yml tags: diff --git a/playbooks/common/openshift-cluster/update_repos_and_packages.yml b/playbooks/common/openshift-cluster/update_repos_and_packages.yml index b83e4d821..be956fca5 100644 --- a/playbooks/common/openshift-cluster/update_repos_and_packages.yml +++ b/playbooks/common/openshift-cluster/update_repos_and_packages.yml @@ -3,8 +3,6 @@ - name: Subscribe hosts, update repos and update OS packages hosts: oo_hosts_to_update - vars: - openshift_deployment_type: "{{ deployment_type }}" roles: # Explicitly calling openshift_facts because it appears that when # rhel_subscribe is skipped that the openshift_facts dependency for diff --git a/playbooks/common/openshift-cluster/upgrades/init.yml b/playbooks/common/openshift-cluster/upgrades/init.yml index a3b8c489e..bcbc4ee02 100644 --- a/playbooks/common/openshift-cluster/upgrades/init.yml +++ b/playbooks/common/openshift-cluster/upgrades/init.yml @@ -29,7 +29,6 @@ g_new_master_hosts: [] g_new_node_hosts: [] openshift_cluster_id: "{{ cluster_id | default('default') }}" - openshift_deployment_type: "{{ deployment_type }}" - name: Set oo_options hosts: oo_all_hosts diff --git a/playbooks/common/openshift-cluster/upgrades/post_control_plane.yml b/playbooks/common/openshift-cluster/upgrades/post_control_plane.yml index 6f096f705..c00795a8d 100644 --- a/playbooks/common/openshift-cluster/upgrades/post_control_plane.yml +++ b/playbooks/common/openshift-cluster/upgrades/post_control_plane.yml @@ -5,7 +5,6 @@ - name: Upgrade default router and default registry hosts: oo_first_master vars: - openshift_deployment_type: "{{ deployment_type }}" registry_image: "{{ openshift.master.registry_url | replace( '${component}', 'docker-registry' ) | replace ( '${version}', openshift_image_tag ) }}" router_image: "{{ openshift.master.registry_url | replace( '${component}', 'haproxy-router' ) | replace ( '${version}', openshift_image_tag ) }}" oc_cmd: "{{ openshift.common.client_binary }} --config={{ openshift.common.config_base }}/master/admin.kubeconfig" diff --git a/playbooks/common/openshift-master/config.yml b/playbooks/common/openshift-master/config.yml index 81c922043..60cf56108 100644 --- a/playbooks/common/openshift-master/config.yml +++ b/playbooks/common/openshift-master/config.yml @@ -48,12 +48,6 @@ - set_fact: openshift_hosted_metrics_resolution: "{{ lookup('oo_option', 'openshift_hosted_metrics_resolution') | default('10s', true) }}" when: openshift_hosted_metrics_resolution is not defined - - set_fact: - openshift_hosted_metrics_deployer_prefix: "{{ lookup('oo_option', 'openshift_hosted_metrics_deployer_prefix') | default('openshift') }}" - when: openshift_hosted_metrics_deployer_prefix is not defined - - set_fact: - openshift_hosted_metrics_deployer_version: "{{ lookup('oo_option', 'openshift_hosted_metrics_deployer_version') | default('latest') }}" - when: openshift_hosted_metrics_deployer_version is not defined roles: - openshift_facts post_tasks: diff --git a/playbooks/common/openshift-master/scaleup.yml b/playbooks/common/openshift-master/scaleup.yml index c59747081..92f16dc47 100644 --- a/playbooks/common/openshift-master/scaleup.yml +++ b/playbooks/common/openshift-master/scaleup.yml @@ -61,9 +61,6 @@ - openshift_docker - include: ../openshift-cluster/disable_excluder.yml - vars: - # the excluders needs to be disabled no matter what status says - with_status_check: false tags: - always diff --git a/playbooks/common/openshift-node/scaleup.yml b/playbooks/common/openshift-node/scaleup.yml index d81bd152e..c31aca62b 100644 --- a/playbooks/common/openshift-node/scaleup.yml +++ b/playbooks/common/openshift-node/scaleup.yml @@ -28,9 +28,6 @@ - openshift_docker - include: ../openshift-cluster/disable_excluder.yml - vars: - # the excluders needs to be disabled no matter what status says - with_status_check: false tags: - always diff --git a/roles/etcd/templates/etcd.conf.j2 b/roles/etcd/templates/etcd.conf.j2 index 990a86c21..9151dd0bd 100644 --- a/roles/etcd/templates/etcd.conf.j2 +++ b/roles/etcd/templates/etcd.conf.j2 @@ -60,3 +60,9 @@ ETCD_PEER_CA_FILE={{ etcd_peer_ca_file }} ETCD_PEER_CERT_FILE={{ etcd_peer_cert_file }} ETCD_PEER_KEY_FILE={{ etcd_peer_key_file }} {% endif -%} + +#[logging] +ETCD_DEBUG="{{ etcd_debug | default(false) | string }}" +{% if etcd_log_package_levels is defined %} +ETCD_LOG_PACKAGE_LEVELS="{{ etcd_log_package_levels }}" +{% endif %} diff --git a/roles/lib_openshift/library/oc_adm_ca_server_cert.py b/roles/lib_openshift/library/oc_adm_ca_server_cert.py index af1d13fe1..2f6026fbf 100644 --- a/roles/lib_openshift/library/oc_adm_ca_server_cert.py +++ b/roles/lib_openshift/library/oc_adm_ca_server_cert.py @@ -130,6 +130,12 @@ options: required: false default: True aliases: [] + expire_days: + description + - Validity of the certificate in days + required: false + default: None + aliases: [] author: - "Kenny Woodson <kwoodson@redhat.com>" extends_documentation_fragment: [] @@ -149,8 +155,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/ca_server_cert -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -184,13 +188,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -206,13 +210,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -234,7 +238,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -323,7 +327,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -423,7 +427,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -542,8 +546,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -604,7 +608,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -630,7 +644,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -662,114 +676,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] - if module.params['src']: + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- @@ -1480,6 +1529,7 @@ class CAServerCert(OpenShiftCLI): 'signer_cert': {'value': params['signer_cert'], 'include': True}, 'signer_key': {'value': params['signer_key'], 'include': True}, 'signer_serial': {'value': params['signer_serial'], 'include': True}, + 'expire_days': {'value': params['expire_days'], 'include': True}, 'backup': {'value': params['backup'], 'include': False}, }) @@ -1538,6 +1588,7 @@ def main(): signer_key=dict(default='/etc/origin/master/ca.key', type='str'), signer_serial=dict(default='/etc/origin/master/ca.serial.txt', type='str'), hostnames=dict(default=[], type='list'), + expire_days=dict(default=None, type='int'), ), supports_check_mode=True, ) diff --git a/roles/lib_openshift/library/oc_adm_manage_node.py b/roles/lib_openshift/library/oc_adm_manage_node.py index 0050ccf62..5f49eef39 100644 --- a/roles/lib_openshift/library/oc_adm_manage_node.py +++ b/roles/lib_openshift/library/oc_adm_manage_node.py @@ -141,8 +141,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/manage_node -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -176,13 +174,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -198,13 +196,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -226,7 +224,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -315,7 +313,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -415,7 +413,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -534,8 +532,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -596,7 +594,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -622,7 +630,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -654,114 +662,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] - if module.params['src']: + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) + + elif params['edits'] is not None: + edits = params['edits'] - if rval[0] and module.params['src']: + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_policy_group.py b/roles/lib_openshift/library/oc_adm_policy_group.py index 3d1dc1c96..7caba04f5 100644 --- a/roles/lib_openshift/library/oc_adm_policy_group.py +++ b/roles/lib_openshift/library/oc_adm_policy_group.py @@ -127,8 +127,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/policy_group -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -162,13 +160,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -184,13 +182,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -212,7 +210,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -301,7 +299,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -401,7 +399,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -520,8 +518,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -582,7 +580,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -608,7 +616,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -640,114 +648,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] - if module.params['src']: + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_policy_user.py b/roles/lib_openshift/library/oc_adm_policy_user.py index 83f2165a3..aac3f7166 100644 --- a/roles/lib_openshift/library/oc_adm_policy_user.py +++ b/roles/lib_openshift/library/oc_adm_policy_user.py @@ -127,8 +127,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/policy_user -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -162,13 +160,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -184,13 +182,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -212,7 +210,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -301,7 +299,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -401,7 +399,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -520,8 +518,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -582,7 +580,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -608,7 +616,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -640,114 +648,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] - if module.params['src']: + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_registry.py b/roles/lib_openshift/library/oc_adm_registry.py index 3a892971b..b0345b026 100644 --- a/roles/lib_openshift/library/oc_adm_registry.py +++ b/roles/lib_openshift/library/oc_adm_registry.py @@ -245,8 +245,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/registry -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -280,13 +278,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -302,13 +300,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -330,7 +328,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -419,7 +417,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -519,7 +517,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -638,8 +636,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -700,7 +698,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -726,7 +734,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -758,114 +766,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] - if module.params['src']: + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_router.py b/roles/lib_openshift/library/oc_adm_router.py index e666e0d09..307269da4 100644 --- a/roles/lib_openshift/library/oc_adm_router.py +++ b/roles/lib_openshift/library/oc_adm_router.py @@ -270,8 +270,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/router -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -305,13 +303,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -327,13 +325,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -355,7 +353,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -444,7 +442,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -544,7 +542,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -663,8 +661,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -725,7 +723,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -751,7 +759,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -783,114 +791,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_configmap.py b/roles/lib_openshift/library/oc_configmap.py index 90d38c7a9..96345ffe0 100644 --- a/roles/lib_openshift/library/oc_configmap.py +++ b/roles/lib_openshift/library/oc_configmap.py @@ -125,8 +125,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/configmap -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -160,13 +158,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -182,13 +180,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -210,7 +208,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -299,7 +297,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -399,7 +397,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -518,8 +516,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -580,7 +578,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -606,7 +614,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -638,114 +646,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_edit.py b/roles/lib_openshift/library/oc_edit.py index 42f50ebe7..99027c07f 100644 --- a/roles/lib_openshift/library/oc_edit.py +++ b/roles/lib_openshift/library/oc_edit.py @@ -169,8 +169,6 @@ oc_edit: # -*- -*- -*- End included fragment: doc/edit -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -204,13 +202,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -226,13 +224,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -254,7 +252,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -343,7 +341,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -443,7 +441,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -562,8 +560,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -624,7 +622,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -650,7 +658,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -682,114 +690,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] - if module.params['src']: + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) + + elif params['edits'] is not None: + edits = params['edits'] - if rval[0] and module.params['src']: + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_env.py b/roles/lib_openshift/library/oc_env.py index 3088ea947..34f86a478 100644 --- a/roles/lib_openshift/library/oc_env.py +++ b/roles/lib_openshift/library/oc_env.py @@ -136,8 +136,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/env -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -171,13 +169,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -193,13 +191,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -221,7 +219,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -310,7 +308,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -410,7 +408,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -529,8 +527,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -591,7 +589,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -617,7 +625,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -649,114 +657,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_group.py b/roles/lib_openshift/library/oc_group.py index 44611df82..00d67108d 100644 --- a/roles/lib_openshift/library/oc_group.py +++ b/roles/lib_openshift/library/oc_group.py @@ -109,8 +109,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/group -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -144,13 +142,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -166,13 +164,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -194,7 +192,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -283,7 +281,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -383,7 +381,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -502,8 +500,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -564,7 +562,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -590,7 +598,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -622,114 +630,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_image.py b/roles/lib_openshift/library/oc_image.py index b7cc37264..ee918a2d1 100644 --- a/roles/lib_openshift/library/oc_image.py +++ b/roles/lib_openshift/library/oc_image.py @@ -128,8 +128,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/image -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -163,13 +161,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -185,13 +183,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -213,7 +211,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -302,7 +300,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -402,7 +400,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -521,8 +519,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -583,7 +581,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -609,7 +617,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -641,114 +649,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_label.py b/roles/lib_openshift/library/oc_label.py index cfcb15241..62b6049c4 100644 --- a/roles/lib_openshift/library/oc_label.py +++ b/roles/lib_openshift/library/oc_label.py @@ -145,8 +145,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/label -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -180,13 +178,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -202,13 +200,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -230,7 +228,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -319,7 +317,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -419,7 +417,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -538,8 +536,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -600,7 +598,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -626,7 +634,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -658,114 +666,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_obj.py b/roles/lib_openshift/library/oc_obj.py index f5cba696d..075c286e0 100644 --- a/roles/lib_openshift/library/oc_obj.py +++ b/roles/lib_openshift/library/oc_obj.py @@ -148,8 +148,6 @@ register: router_output # -*- -*- -*- End included fragment: doc/obj -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -183,13 +181,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -205,13 +203,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -233,7 +231,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -322,7 +320,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -422,7 +420,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -541,8 +539,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -603,7 +601,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -629,7 +637,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -661,114 +669,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_objectvalidator.py b/roles/lib_openshift/library/oc_objectvalidator.py index 4e1e769cf..d65e1d4c9 100644 --- a/roles/lib_openshift/library/oc_objectvalidator.py +++ b/roles/lib_openshift/library/oc_objectvalidator.py @@ -80,8 +80,6 @@ oc_objectvalidator: # -*- -*- -*- End included fragment: doc/objectvalidator -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -115,13 +113,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -137,13 +135,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -165,7 +163,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -254,7 +252,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -354,7 +352,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -473,8 +471,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -535,7 +533,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -561,7 +569,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -593,114 +601,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] - if module.params['src']: + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_process.py b/roles/lib_openshift/library/oc_process.py index cabb2ff29..d487746eb 100644 --- a/roles/lib_openshift/library/oc_process.py +++ b/roles/lib_openshift/library/oc_process.py @@ -137,8 +137,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/process -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -172,13 +170,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -194,13 +192,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -222,7 +220,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -311,7 +309,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -411,7 +409,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -530,8 +528,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -592,7 +590,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -618,7 +626,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -650,114 +658,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_project.py b/roles/lib_openshift/library/oc_project.py index 7700a83a3..3fddce055 100644 --- a/roles/lib_openshift/library/oc_project.py +++ b/roles/lib_openshift/library/oc_project.py @@ -134,8 +134,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/project -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -169,13 +167,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -191,13 +189,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -219,7 +217,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -308,7 +306,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -408,7 +406,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -527,8 +525,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -589,7 +587,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -615,7 +623,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -647,114 +655,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_pvc.py b/roles/lib_openshift/library/oc_pvc.py index df0b0d86a..d63f6e063 100644 --- a/roles/lib_openshift/library/oc_pvc.py +++ b/roles/lib_openshift/library/oc_pvc.py @@ -129,8 +129,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/pvc -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -164,13 +162,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -186,13 +184,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -214,7 +212,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -303,7 +301,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -403,7 +401,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -522,8 +520,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -584,7 +582,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -610,7 +618,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -642,114 +650,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_route.py b/roles/lib_openshift/library/oc_route.py index fe59cca33..daddec69f 100644 --- a/roles/lib_openshift/library/oc_route.py +++ b/roles/lib_openshift/library/oc_route.py @@ -179,8 +179,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/route -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -214,13 +212,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -236,13 +234,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -264,7 +262,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -353,7 +351,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -453,7 +451,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -572,8 +570,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -634,7 +632,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -660,7 +668,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -692,114 +700,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_scale.py b/roles/lib_openshift/library/oc_scale.py index 98f1d94a7..92e9362be 100644 --- a/roles/lib_openshift/library/oc_scale.py +++ b/roles/lib_openshift/library/oc_scale.py @@ -123,8 +123,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/scale -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -158,13 +156,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -180,13 +178,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -208,7 +206,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -297,7 +295,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -397,7 +395,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -516,8 +514,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -578,7 +576,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -604,7 +612,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -636,114 +644,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_secret.py b/roles/lib_openshift/library/oc_secret.py index deba4ab8a..1ffdce4df 100644 --- a/roles/lib_openshift/library/oc_secret.py +++ b/roles/lib_openshift/library/oc_secret.py @@ -169,8 +169,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/secret -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -204,13 +202,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -226,13 +224,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -254,7 +252,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -343,7 +341,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -443,7 +441,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -562,8 +560,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -624,7 +622,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -650,7 +658,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -682,114 +690,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_service.py b/roles/lib_openshift/library/oc_service.py index c2e91e39e..77056d5de 100644 --- a/roles/lib_openshift/library/oc_service.py +++ b/roles/lib_openshift/library/oc_service.py @@ -175,8 +175,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/service -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -210,13 +208,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -232,13 +230,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -260,7 +258,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -349,7 +347,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -449,7 +447,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -568,8 +566,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -630,7 +628,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -656,7 +664,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -688,114 +696,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_serviceaccount.py b/roles/lib_openshift/library/oc_serviceaccount.py index a1d8fff14..807bfc992 100644 --- a/roles/lib_openshift/library/oc_serviceaccount.py +++ b/roles/lib_openshift/library/oc_serviceaccount.py @@ -121,8 +121,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/serviceaccount -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -156,13 +154,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -178,13 +176,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -206,7 +204,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -295,7 +293,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -395,7 +393,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -514,8 +512,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -576,7 +574,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -602,7 +610,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -634,114 +642,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_serviceaccount_secret.py b/roles/lib_openshift/library/oc_serviceaccount_secret.py index 470043cc6..c8f4ebef7 100644 --- a/roles/lib_openshift/library/oc_serviceaccount_secret.py +++ b/roles/lib_openshift/library/oc_serviceaccount_secret.py @@ -121,8 +121,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/serviceaccount_secret -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -156,13 +154,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -178,13 +176,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -206,7 +204,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -295,7 +293,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -395,7 +393,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -514,8 +512,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -576,7 +574,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -602,7 +610,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -634,114 +642,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_user.py b/roles/lib_openshift/library/oc_user.py index 2ccc00301..aa9f07980 100644 --- a/roles/lib_openshift/library/oc_user.py +++ b/roles/lib_openshift/library/oc_user.py @@ -181,8 +181,6 @@ ok: [ded-int-aws-master-61034] => { # -*- -*- -*- End included fragment: doc/user -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -216,13 +214,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -238,13 +236,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -266,7 +264,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -355,7 +353,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -455,7 +453,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -574,8 +572,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -636,7 +634,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -662,7 +670,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -694,114 +702,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_version.py b/roles/lib_openshift/library/oc_version.py index 378c2b2e5..eb293322d 100644 --- a/roles/lib_openshift/library/oc_version.py +++ b/roles/lib_openshift/library/oc_version.py @@ -93,8 +93,6 @@ oc_version: # -*- -*- -*- End included fragment: doc/version -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -128,13 +126,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -150,13 +148,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -178,7 +176,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -267,7 +265,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -367,7 +365,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -486,8 +484,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -548,7 +546,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -574,7 +582,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -606,114 +614,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] - if module.params['src']: + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_volume.py b/roles/lib_openshift/library/oc_volume.py index e9e29468a..23b292763 100644 --- a/roles/lib_openshift/library/oc_volume.py +++ b/roles/lib_openshift/library/oc_volume.py @@ -158,8 +158,6 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/volume -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -193,13 +191,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -215,13 +213,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -243,7 +241,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -332,7 +330,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -432,7 +430,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -551,8 +549,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -613,7 +611,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -639,7 +647,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -671,114 +679,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- @@ -1941,7 +1984,7 @@ class OCVolume(OpenShiftCLI): if not oc_volume.exists(): if check_mode: - exit_json(changed=False, msg='Would have performed a create.') + return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'} # Create it here api_rval = oc_volume.put() diff --git a/roles/lib_openshift/src/ansible/oc_adm_ca_server_cert.py b/roles/lib_openshift/src/ansible/oc_adm_ca_server_cert.py index c80c2eb44..10f1c9b4b 100644 --- a/roles/lib_openshift/src/ansible/oc_adm_ca_server_cert.py +++ b/roles/lib_openshift/src/ansible/oc_adm_ca_server_cert.py @@ -20,6 +20,7 @@ def main(): signer_key=dict(default='/etc/origin/master/ca.key', type='str'), signer_serial=dict(default='/etc/origin/master/ca.serial.txt', type='str'), hostnames=dict(default=[], type='list'), + expire_days=dict(default=None, type='int'), ), supports_check_mode=True, ) diff --git a/roles/lib_openshift/src/class/oc_adm_ca_server_cert.py b/roles/lib_openshift/src/class/oc_adm_ca_server_cert.py index 18c69f2fa..fa0c4e3af 100644 --- a/roles/lib_openshift/src/class/oc_adm_ca_server_cert.py +++ b/roles/lib_openshift/src/class/oc_adm_ca_server_cert.py @@ -102,6 +102,7 @@ class CAServerCert(OpenShiftCLI): 'signer_cert': {'value': params['signer_cert'], 'include': True}, 'signer_key': {'value': params['signer_key'], 'include': True}, 'signer_serial': {'value': params['signer_serial'], 'include': True}, + 'expire_days': {'value': params['expire_days'], 'include': True}, 'backup': {'value': params['backup'], 'include': False}, }) diff --git a/roles/lib_openshift/src/class/oc_volume.py b/roles/lib_openshift/src/class/oc_volume.py index 5211a1afd..45b58a516 100644 --- a/roles/lib_openshift/src/class/oc_volume.py +++ b/roles/lib_openshift/src/class/oc_volume.py @@ -157,7 +157,7 @@ class OCVolume(OpenShiftCLI): if not oc_volume.exists(): if check_mode: - exit_json(changed=False, msg='Would have performed a create.') + return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'} # Create it here api_rval = oc_volume.put() diff --git a/roles/lib_openshift/src/doc/ca_server_cert b/roles/lib_openshift/src/doc/ca_server_cert index ff9229281..7f2be4ada 100644 --- a/roles/lib_openshift/src/doc/ca_server_cert +++ b/roles/lib_openshift/src/doc/ca_server_cert @@ -79,6 +79,12 @@ options: required: false default: True aliases: [] + expire_days: + description + - Validity of the certificate in days + required: false + default: None + aliases: [] author: - "Kenny Woodson <kwoodson@redhat.com>" extends_documentation_fragment: [] diff --git a/roles/lib_utils/library/yedit.py b/roles/lib_utils/library/yedit.py index a2ae6b4f6..9adaeeb52 100644 --- a/roles/lib_utils/library/yedit.py +++ b/roles/lib_utils/library/yedit.py @@ -180,13 +180,27 @@ EXAMPLES = ''' # a: # b: # c: d +# +# multiple edits at the same time +- name: perform multiple edits + yedit: + src: somefile.yml + edits: + - key: a#b#c + value: d + - key: a#b#c#d + value: e + state: present +# Results: +# a: +# b: +# c: +# d: e ''' # -*- -*- -*- End included fragment: doc/yedit -*- -*- -*- # -*- -*- -*- Begin included fragment: class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 class YeditException(Exception): @@ -220,13 +234,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -242,13 +256,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -270,7 +284,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -359,7 +373,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -459,7 +473,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -578,8 +592,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -640,7 +654,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -666,7 +690,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -698,114 +722,149 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) - if module.params['src']: + state = params['state'] + + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) - if rval[0] and module.params['src']: + elif params['edits'] is not None: + edits = params['edits'] + + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} # -*- -*- -*- End included fragment: class/yedit.py -*- -*- -*- @@ -837,12 +896,34 @@ def main(): type='str'), backup=dict(default=True, type='bool'), separator=dict(default='.', type='str'), + edits=dict(default=None, type='list'), ), mutually_exclusive=[["curr_value", "index"], ['update', "append"]], required_one_of=[["content", "src"]], ) - rval = Yedit.run_ansible(module) + # Verify we recieved either a valid key or edits with valid keys when receiving a src file. + # A valid key being not None or not ''. + if module.params['src'] is not None: + key_error = False + edit_error = False + + if module.params['key'] in [None, '']: + key_error = True + + if module.params['edits'] in [None, []]: + edit_error = True + + else: + for edit in module.params['edits']: + if edit.get('key') in [None, '']: + edit_error = True + break + + if key_error and edit_error: + module.fail_json(failed=True, msg='Empty value for parameter key not allowed.') + + rval = Yedit.run_ansible(module.params) if 'failed' in rval and rval['failed']: module.fail_json(**rval) diff --git a/roles/lib_utils/src/ansible/yedit.py b/roles/lib_utils/src/ansible/yedit.py index 8a1a7c2dc..c4b818cf1 100644 --- a/roles/lib_utils/src/ansible/yedit.py +++ b/roles/lib_utils/src/ansible/yedit.py @@ -26,12 +26,34 @@ def main(): type='str'), backup=dict(default=True, type='bool'), separator=dict(default='.', type='str'), + edits=dict(default=None, type='list'), ), mutually_exclusive=[["curr_value", "index"], ['update', "append"]], required_one_of=[["content", "src"]], ) - rval = Yedit.run_ansible(module) + # Verify we recieved either a valid key or edits with valid keys when receiving a src file. + # A valid key being not None or not ''. + if module.params['src'] is not None: + key_error = False + edit_error = False + + if module.params['key'] in [None, '']: + key_error = True + + if module.params['edits'] in [None, []]: + edit_error = True + + else: + for edit in module.params['edits']: + if edit.get('key') in [None, '']: + edit_error = True + break + + if key_error and edit_error: + module.fail_json(failed=True, msg='Empty value for parameter key not allowed.') + + rval = Yedit.run_ansible(module.params) if 'failed' in rval and rval['failed']: module.fail_json(**rval) diff --git a/roles/lib_utils/src/class/yedit.py b/roles/lib_utils/src/class/yedit.py index 533665db2..e0a27012f 100644 --- a/roles/lib_utils/src/class/yedit.py +++ b/roles/lib_utils/src/class/yedit.py @@ -1,6 +1,5 @@ # flake8: noqa -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 +# pylint: skip-file class YeditException(Exception): @@ -34,13 +33,13 @@ class Yedit(object): @property def separator(self): - ''' getter method for yaml_dict ''' + ''' getter method for separator ''' return self._separator @separator.setter - def separator(self): - ''' getter method for yaml_dict ''' - return self._separator + def separator(self, inc_sep): + ''' setter method for separator ''' + self._separator = inc_sep @property def yaml_dict(self): @@ -56,13 +55,13 @@ class Yedit(object): 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) + return re.findall(Yedit.re_key.format(''.join(common_separators)), key) @staticmethod def valid_key(key, sep='.'): '''validate the incoming key''' common_separators = list(Yedit.com_sep - set([sep])) - if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): + if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key): return False return True @@ -84,7 +83,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -173,7 +172,7 @@ class Yedit(object): 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) + data = data.get(dict_key) elif (arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] @@ -273,7 +272,7 @@ class Yedit(object): 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) + raise YeditException('Problem with loading yaml file. {}'.format(err)) return self.yaml_dict @@ -392,8 +391,8 @@ class Yedit(object): # AUDIT:maybe-no-member makes sense due to fuzzy types # pylint: disable=maybe-no-member if not isinstance(value, dict): - raise YeditException('Cannot replace key, value entry in ' + - 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501 + raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + + 'value=[{}] type=[{}]'.format(value, type(value))) entry.update(value) return (True, self.yaml_dict) @@ -454,7 +453,17 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if not result: + if result is None: + return (False, self.yaml_dict) + + # When path equals "" it is a special case. + # "" refers to the root of the document + # Only update the root path (entire document) when its a list or dict + if path == '': + if isinstance(result, list) or isinstance(result, dict): + self.yaml_dict = result + return (True, self.yaml_dict) + return (False, self.yaml_dict) self.yaml_dict = tmp_copy @@ -480,7 +489,7 @@ class Yedit(object): pass result = Yedit.add_entry(tmp_copy, path, value, self.separator) - if result: + if result is not None: self.yaml_dict = tmp_copy return (True, self.yaml_dict) @@ -512,112 +521,147 @@ class Yedit(object): # 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)) + raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype)) elif isinstance(inc_value, bool) and 'str' in vtype: inc_value = str(inc_value) + # There is a special case where '' will turn into None after yaml loading it so skip + if isinstance(inc_value, str) and inc_value == '': + pass # If vtype is not str then go ahead and attempt to yaml load it. - if isinstance(inc_value, str) and 'str' not in vtype: + elif isinstance(inc_value, str) and 'str' not in vtype: try: - inc_value = yaml.load(inc_value) + inc_value = yaml.safe_load(inc_value) except Exception: - raise YeditException('Could not determine type of incoming ' + - 'value. value=[%s] vtype=[%s]' - % (type(inc_value), vtype)) + raise YeditException('Could not determine type of incoming value. ' + + 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype)) return inc_value + @staticmethod + def process_edits(edits, yamlfile): + '''run through a list of edits and process them one-by-one''' + results = [] + for edit in edits: + value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) + if edit.get('action') == 'update': + # pylint: disable=line-too-long + curr_value = Yedit.get_curr_value( + Yedit.parse_value(edit.get('curr_value')), + edit.get('curr_value_format')) + + rval = yamlfile.update(edit['key'], + value, + edit.get('index'), + curr_value) + + elif edit.get('action') == 'append': + rval = yamlfile.append(edit['key'], value) + + else: + rval = yamlfile.put(edit['key'], value) + + if rval[0]: + results.append({'key': edit['key'], 'edit': rval[1]}) + + return {'changed': len(results) > 0, 'results': results} + # pylint: disable=too-many-return-statements,too-many-branches @staticmethod - def run_ansible(module): + def run_ansible(params): '''perform the idempotent crud operations''' - yamlfile = Yedit(filename=module.params['src'], - backup=module.params['backup'], - separator=module.params['separator']) + yamlfile = Yedit(filename=params['src'], + backup=params['backup'], + separator=params['separator']) + + state = params['state'] - if module.params['src']: + if params['src']: rval = yamlfile.load() - if yamlfile.yaml_dict is None and \ - module.params['state'] != 'present': + if yamlfile.yaml_dict is None and 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']) + 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) + + 'file exists, that it is has correct permissions, and is valid yaml.'} + + if state == 'list': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['key']: - rval = yamlfile.get(module.params['key']) or {} + if params['key']: + rval = yamlfile.get(params['key']) or {} - return {'changed': False, 'result': rval, 'state': "list"} + return {'changed': False, 'result': rval, 'state': state} - elif module.params['state'] == 'absent': - if module.params['content']: - content = Yedit.parse_value(module.params['content'], - module.params['content_type']) + elif state == 'absent': + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) yamlfile.yaml_dict = content - if module.params['update']: - rval = yamlfile.pop(module.params['key'], - module.params['value']) + if params['update']: + rval = yamlfile.pop(params['key'], params['value']) else: - rval = yamlfile.delete(module.params['key']) + rval = yamlfile.delete(params['key']) - if rval[0] and module.params['src']: + if rval[0] and params['src']: yamlfile.write() - return {'changed': rval[0], 'result': rval[1], 'state': "absent"} + return {'changed': rval[0], 'result': rval[1], 'state': state} - elif module.params['state'] == 'present': + elif 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']) + if params['content']: + content = Yedit.parse_value(params['content'], params['content_type']) # We had no edits to make and the contents are the same if yamlfile.yaml_dict == content and \ - module.params['value'] is None: - return {'changed': False, - 'result': yamlfile.yaml_dict, - 'state': "present"} + params['value'] is None: + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} 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 + # If we were passed a key, value then + # we enapsulate it in a list and process it + # Key, Value passed to the module : Converted to Edits list # + edits = [] + _edit = {} + if params['value'] is not None: + _edit['value'] = params['value'] + _edit['value_type'] = params['value_type'] + _edit['key'] = params['key'] - rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501 + if params['update']: + _edit['action'] = 'update' + _edit['curr_value'] = params['curr_value'] + _edit['curr_value_format'] = params['curr_value_format'] + _edit['index'] = params['index'] - elif module.params['append']: - rval = yamlfile.append(key, value) - else: - rval = yamlfile.put(key, value) + elif params['append']: + _edit['action'] = 'append' + + edits.append(_edit) + + elif params['edits'] is not None: + edits = params['edits'] - if rval[0] and module.params['src']: + if edits: + results = Yedit.process_edits(edits, yamlfile) + + # if there were changes and a src provided to us we need to write + if results['changed'] and params['src']: yamlfile.write() - return {'changed': rval[0], - 'result': rval[1], 'state': "present"} + return {'changed': results['changed'], 'result': results['results'], 'state': state} # no edits to make - if module.params['src']: + if params['src']: # pylint: disable=redefined-variable-type rval = yamlfile.write() return {'changed': rval[0], 'result': rval[1], - 'state': "present"} + 'state': state} + # We were passed content but no src, key or value, or edits. Return contents in memory + return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state} return {'failed': True, 'msg': 'Unkown state passed'} diff --git a/roles/lib_utils/src/doc/yedit b/roles/lib_utils/src/doc/yedit index 16b44943e..82af1f675 100644 --- a/roles/lib_utils/src/doc/yedit +++ b/roles/lib_utils/src/doc/yedit @@ -135,4 +135,20 @@ EXAMPLES = ''' # a: # b: # c: d +# +# multiple edits at the same time +- name: perform multiple edits + yedit: + src: somefile.yml + edits: + - key: a#b#c + value: d + - key: a#b#c#d + value: e + state: present +# Results: +# a: +# b: +# c: +# d: e ''' diff --git a/roles/lib_utils/src/test/integration/kube-manager-test.yaml.orig b/roles/lib_utils/src/test/integration/kube-manager-test.yaml.orig deleted file mode 100644 index 5541c3dae..000000000 --- a/roles/lib_utils/src/test/integration/kube-manager-test.yaml.orig +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: kube-controller-manager - namespace: kube-system -spec: - hostNetwork: true - containers: - - name: kube-controller-manager - image: openshift/kube:v1.0.0 - command: - - /hyperkube - - controller-manager - - --master=http://127.0.0.1:8080 - - --leader-elect=true - - --service-account-private-key-file=/etc/kubernetes/ssl/apiserver-key.pem - - --root-ca-file=/etc/k8s/ssl/my.pem - - --my-new-parameter=openshift - livenessProbe: - httpGet: - host: 127.0.0.1 - path: /healthz - port: 10252 - initialDelaySeconds: 15 - timeoutSeconds: 1 - volumeMounts: - - mountPath: /etc/kubernetes/ssl - name: ssl-certs-kubernetes - readOnly: true - - mountPath: /etc/ssl/certs - name: ssl-certs-host - readOnly: 'true' - volumes: - - hostPath: - path: /etc/kubernetes/ssl - name: ssl-certs-kubernetes - - hostPath: - path: /usr/share/ca-certificates - name: ssl-certs-host -yedittest: yedittest -metadata-namespace: openshift-is-awesome -nonexistingkey: -- --my-new-parameter=openshift -a: - b: - c: d -e: - f: - g: - h: - i: - j: k diff --git a/roles/lib_utils/src/test/integration/yedit.yml b/roles/lib_utils/src/test/integration/yedit.yml index e3dfd490b..65209bade 100755 --- a/roles/lib_utils/src/test/integration/yedit.yml +++ b/roles/lib_utils/src/test/integration/yedit.yml @@ -219,4 +219,33 @@ assert: that: results.result == [1, 2, 3] msg: "Test: '[1, 2, 3]' != [{{ results.result }}]" -###### end test create list value ##### + ###### end test create list value ##### + + ###### test create multiple list value ##### + - name: test multiple edits + yedit: + src: "{{ test_file }}" + edits: + - key: z.x.y + value: + - 1 + - 2 + - 3 + - key: z.x.y + value: 4 + action: append + + - name: retrieve the key + yedit: + src: "{{ test_file }}" + state: list + key: z#x#y + separator: '#' + register: results + - debug: var=results + + - name: Assert that the key was created + assert: + that: results.result == [1, 2, 3, 4] + msg: "Test: '[1, 2, 3, 4]' != [{{ results.result }}]" + ###### end test create multiple list value ##### diff --git a/roles/lib_utils/src/test/unit/test_yedit.py b/roles/lib_utils/src/test/unit/test_yedit.py index 23a3f7353..f9f42843a 100755 --- a/roles/lib_utils/src/test/unit/test_yedit.py +++ b/roles/lib_utils/src/test/unit/test_yedit.py @@ -5,6 +5,7 @@ import os import sys import unittest +import mock # Removing invalid variable names for tests so that I can # keep them brief @@ -277,6 +278,91 @@ class YeditTest(unittest.TestCase): with self.assertRaises(YeditException): yed.put('new.stuff.here[0]', 'item') + def test_empty_key_with_int_value(self): + '''test editing top level with not list or dict''' + yed = Yedit(content={'a': {'b': 12}}) + result = yed.put('', 'b') + self.assertFalse(result[0]) + + def test_setting_separator(self): + '''test editing top level with not list or dict''' + yed = Yedit(content={'a': {'b': 12}}) + yed.separator = ':' + self.assertEqual(yed.separator, ':') + + def test_remove_all(self): + '''test removing all data''' + data = Yedit.remove_entry({'a': {'b': 12}}, '') + self.assertTrue(data) + + def test_remove_list_entry(self): + '''test removing list entry''' + data = {'a': {'b': [{'c': 3}]}} + results = Yedit.remove_entry(data, 'a.b[0]') + self.assertTrue(results) + self.assertTrue(data, {'a': {'b': []}}) + + def test_parse_value_string_true(self): + '''test parse_value''' + results = Yedit.parse_value('true', 'str') + self.assertEqual(results, 'true') + + def test_parse_value_bool_true(self): + '''test parse_value''' + results = Yedit.parse_value('true', 'bool') + self.assertTrue(results) + + def test_parse_value_bool_exception(self): + '''test parse_value''' + with self.assertRaises(YeditException): + Yedit.parse_value('TTT', 'bool') + + @mock.patch('yedit.Yedit.write') + def test_run_ansible_basic(self, mock_write): + '''test parse_value''' + params = { + 'src': None, + 'backup': False, + 'separator': '.', + 'state': 'present', + 'edits': [], + 'value': None, + 'key': None, + 'content': {'a': {'b': {'c': 1}}}, + 'content_type': '', + } + + results = Yedit.run_ansible(params) + + mock_write.side_effect = [ + (True, params['content']), + ] + + self.assertFalse(results['changed']) + + @mock.patch('yedit.Yedit.write') + def test_run_ansible_and_write(self, mock_write): + '''test parse_value''' + params = { + 'src': '/tmp/test', + 'backup': False, + 'separator': '.', + 'state': 'present', + 'edits': [], + 'value': None, + 'key': None, + 'content': {'a': {'b': {'c': 1}}}, + 'content_type': '', + } + + results = Yedit.run_ansible(params) + + mock_write.side_effect = [ + (True, params['content']), + ] + + self.assertTrue(results['changed']) + def tearDown(self): '''TearDown method''' os.unlink(YeditTest.filename) diff --git a/roles/openshift_ca/README.md b/roles/openshift_ca/README.md index 96c9cd5f2..dfbe81c6c 100644 --- a/roles/openshift_ca/README.md +++ b/roles/openshift_ca/README.md @@ -19,6 +19,8 @@ From this role: | openshift_ca_key | `{{ openshift_ca_config_dir }}/ca.key` | CA key path including CA key filename. | | openshift_ca_serial | `{{ openshift_ca_config_dir }}/ca.serial.txt` | CA serial path including CA serial filename. | | openshift_version | `{{ openshift_pkg_version }}` | OpenShift package version. | +| openshift_master_cert_expire_days | `730` (2 years) | Validity of the certificates in days. Works only with OpenShift version 1.5 (3.5) and later. | +| openshift_ca_cert_expire_days | `1825` (5 years) | Validity of the CA certificates in days. Works only with OpenShift version 1.5 (3.5) and later. | Dependencies ------------ diff --git a/roles/openshift_ca/defaults/main.yml b/roles/openshift_ca/defaults/main.yml new file mode 100644 index 000000000..ecfcc88b3 --- /dev/null +++ b/roles/openshift_ca/defaults/main.yml @@ -0,0 +1,3 @@ +--- +openshift_ca_cert_expire_days: 1825 +openshift_master_cert_expire_days: 730 diff --git a/roles/openshift_ca/tasks/main.yml b/roles/openshift_ca/tasks/main.yml index 70c2a9121..3b17d9ed6 100644 --- a/roles/openshift_ca/tasks/main.yml +++ b/roles/openshift_ca/tasks/main.yml @@ -88,7 +88,7 @@ # 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: > - {{ hostvars[openshift_ca_host].openshift.common.client_binary }} adm create-master-certs + {{ hostvars[openshift_ca_host].openshift.common.client_binary }} adm ca create-master-certs {% for named_ca_certificate in openshift.master.named_certificates | default([]) | oo_collect('cafile') %} --certificate-authority {{ named_ca_certificate }} {% endfor %} @@ -99,6 +99,10 @@ --master={{ openshift.master.api_url }} --public-master={{ openshift.master.public_api_url }} --cert-dir={{ openshift_ca_config_dir }} + {% if openshift_version | oo_version_gte_3_5_or_1_5(openshift.common.deployment_type) | bool %} + --expire-days={{ openshift_master_cert_expire_days }} + --signer-expire-days={{ openshift_ca_cert_expire_days }} + {% endif %} --overwrite=false when: master_ca_missing | bool or openshift_certificates_redeploy | default(false) | bool delegate_to: "{{ openshift_ca_host }}" diff --git a/roles/openshift_excluder/tasks/disable.yml b/roles/openshift_excluder/tasks/disable.yml index e23496b3b..325d2a4e8 100644 --- a/roles/openshift_excluder/tasks/disable.yml +++ b/roles/openshift_excluder/tasks/disable.yml @@ -1,6 +1,5 @@ --- # input variables -# - with_status_check # - excluder_package_state # - docker_excluder_package_state - include: init.yml @@ -35,6 +34,6 @@ unexclude_docker_excluder: false # disable openshift excluder is never overrided to be enabled # disable it if the docker excluder is enabled - unexclude_openshift_excluder: true + unexclude_openshift_excluder: "{{ openshift_excluder_on | bool }}" when: - not openshift.common.is_atomic | bool diff --git a/roles/openshift_facts/meta/main.yml b/roles/openshift_facts/meta/main.yml index 0be3afd24..7eead2d6e 100644 --- a/roles/openshift_facts/meta/main.yml +++ b/roles/openshift_facts/meta/main.yml @@ -12,4 +12,5 @@ galaxy_info: categories: - cloud - system -dependencies: [] +dependencies: +- role: openshift_sanitize_inventory diff --git a/roles/openshift_facts/tasks/main.yml b/roles/openshift_facts/tasks/main.yml index 73c668c72..93972f0e0 100644 --- a/roles/openshift_facts/tasks/main.yml +++ b/roles/openshift_facts/tasks/main.yml @@ -64,8 +64,7 @@ role: common local_facts: debug_level: "{{ openshift_debug_level | default(2) }}" - # TODO: Deprecate deployment_type in favor of openshift_deployment_type - deployment_type: "{{ openshift_deployment_type | default(deployment_type) }}" + deployment_type: "{{ openshift_deployment_type }}" deployment_subtype: "{{ openshift_deployment_subtype | default(None) }}" cluster_id: "{{ openshift_cluster_id | default('default') }}" hostname: "{{ openshift_hostname | default(None) }}" diff --git a/roles/openshift_hosted/README.md b/roles/openshift_hosted/README.md index 328f800bf..6d576df71 100644 --- a/roles/openshift_hosted/README.md +++ b/roles/openshift_hosted/README.md @@ -26,6 +26,7 @@ From this role: | openshift_hosted_registry_registryurl | 'openshift3/ose-${component}:${version}' | The image to base the OpenShift registry on. | | openshift_hosted_registry_replicas | Number of nodes matching selector | The number of replicas to configure. | | openshift_hosted_registry_selector | region=infra | Node selector used when creating registry. The OpenShift registry will only be deployed to nodes matching this selector. | +| openshift_hosted_registry_cert_expire_days | `730` (2 years) | Validity of the certificates in days. Works only with OpenShift version 1.5 (3.5) and later. | Dependencies ------------ diff --git a/roles/openshift_hosted/defaults/main.yml b/roles/openshift_hosted/defaults/main.yml index 32bcd8d08..d73f339f7 100644 --- a/roles/openshift_hosted/defaults/main.yml +++ b/roles/openshift_hosted/defaults/main.yml @@ -28,3 +28,4 @@ openshift_hosted_routers: openshift_hosted_router_certificates: {} +openshift_hosted_registry_cert_expire_days: 730 diff --git a/roles/openshift_hosted/tasks/registry/secure.yml b/roles/openshift_hosted/tasks/registry/secure.yml index f9ea2ebeb..8a159bf73 100644 --- a/roles/openshift_hosted/tasks/registry/secure.yml +++ b/roles/openshift_hosted/tasks/registry/secure.yml @@ -57,6 +57,7 @@ - "{{ docker_registry_route_hostname }}" cert: "{{ openshift_master_config_dir }}/registry.crt" key: "{{ openshift_master_config_dir }}/registry.key" + expire_days: "{{ openshift_hosted_registry_cert_expire_days if openshift_version | oo_version_gte_3_5_or_1_5(openshift.common.deployment_type) | bool else omit }}" register: server_cert_out - name: Create the secret for the registry certificates diff --git a/roles/openshift_logging/tasks/generate_configmaps.yaml b/roles/openshift_logging/tasks/generate_configmaps.yaml index c1721895c..18df00647 100644 --- a/roles/openshift_logging/tasks/generate_configmaps.yaml +++ b/roles/openshift_logging/tasks/generate_configmaps.yaml @@ -7,20 +7,20 @@ changed_when: no - local_action: > - copy content="{{ config_source | combine(override_config,recursive=True) | to_nice_yaml }}" + template src=elasticsearch.yml.j2 dest="{{local_tmp.stdout}}/elasticsearch-gen-template.yml" vars: - config_source: "{{lookup('file','templates/elasticsearch.yml.j2') | from_yaml }}" - override_config: "{{openshift_logging_es_config | from_yaml}}" - when: es_logging_contents is undefined + - allow_cluster_reader: "{{openshift_logging_es_ops_allow_cluster_reader | lower | default('false')}}" + when: es_config_contents is undefined changed_when: no - - template: - src: "{{local_tmp.stdout}}/elasticsearch-gen-template.yml" + - copy: + content: "{{ config_source | combine(override_config,recursive=True) | to_nice_yaml }}" dest: "{{mktemp.stdout}}/elasticsearch.yml" vars: - - allow_cluster_reader: "{{openshift_logging_es_ops_allow_cluster_reader | lower | default('false')}}" - when: es_config_contents is undefined + config_source: "{{lookup('file','{{local_tmp.stdout}}/elasticsearch-gen-template.yml') | from_yaml }}" + override_config: "{{openshift_logging_es_config | from_yaml}}" + when: es_logging_contents is undefined changed_when: no - copy: diff --git a/roles/openshift_logging/templates/elasticsearch.yml.j2 b/roles/openshift_logging/templates/elasticsearch.yml.j2 index 07e8c0c98..93c4d854c 100644 --- a/roles/openshift_logging/templates/elasticsearch.yml.j2 +++ b/roles/openshift_logging/templates/elasticsearch.yml.j2 @@ -49,7 +49,7 @@ openshift.searchguard: keystore.path: /etc/elasticsearch/secret/admin.jks truststore.path: /etc/elasticsearch/secret/searchguard.truststore -openshift.operations.allow_cluster_reader: "{{allow_cluster_reader | default (false)}}" +openshift.operations.allow_cluster_reader: {{allow_cluster_reader | default (false)}} path: data: /elasticsearch/persistent/${CLUSTER_NAME}/data diff --git a/roles/openshift_master_certificates/README.md b/roles/openshift_master_certificates/README.md index a80d47040..4758bbdfb 100644 --- a/roles/openshift_master_certificates/README.md +++ b/roles/openshift_master_certificates/README.md @@ -21,6 +21,7 @@ From this role: |---------------------------------------|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| | openshift_generated_configs_dir | `{{ openshift.common.config_base }}/generated-configs` | Directory in which per-master generated config directories will be created on the `openshift_ca_host`. | | openshift_master_cert_subdir | `master-{{ openshift.common.hostname }}` | Directory within `openshift_generated_configs_dir` where per-master configurations will be placed on the `openshift_ca_host`. | +| openshift_master_cert_expire_days | `730` (2 years) | Validity of the certificates in days. Works only with OpenShift version 1.5 (3.5) and later. | | openshift_master_config_dir | `{{ openshift.common.config_base }}/master` | Master configuration directory in which certificates will be deployed on masters. | | openshift_master_generated_config_dir | `{{ openshift_generated_configs_dir }}/{{ openshift_master_cert_subdir }` | Full path to the per-master generated config directory. | diff --git a/roles/openshift_master_certificates/defaults/main.yml b/roles/openshift_master_certificates/defaults/main.yml new file mode 100644 index 000000000..dba62c4ec --- /dev/null +++ b/roles/openshift_master_certificates/defaults/main.yml @@ -0,0 +1,2 @@ +--- +openshift_master_cert_expire_days: 730 diff --git a/roles/openshift_master_certificates/tasks/main.yml b/roles/openshift_master_certificates/tasks/main.yml index 61541acb8..d4c9a96ca 100644 --- a/roles/openshift_master_certificates/tasks/main.yml +++ b/roles/openshift_master_certificates/tasks/main.yml @@ -57,6 +57,9 @@ --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 + {% if openshift_version | oo_version_gte_3_5_or_1_5(openshift.common.deployment_type) | bool %} + --expire-days={{ openshift_master_cert_expire_days }} + {% endif %} --signer-cert={{ openshift_ca_cert }} --signer-key={{ openshift_ca_key }} --signer-serial={{ openshift_ca_serial }} @@ -84,6 +87,9 @@ --signer-serial={{ openshift_ca_serial }} --user=system:openshift-master --basename=openshift-master + {% if openshift_version | oo_version_gte_3_5_or_1_5(openshift.common.deployment_type) | bool %} + --expire-days={{ openshift_master_cert_expire_days }} + {% endif %} args: creates: "{{ openshift_generated_configs_dir }}/master-{{ hostvars[item].openshift.common.hostname }}/openshift-master.kubeconfig" with_items: "{{ hostvars diff --git a/roles/openshift_node_certificates/README.md b/roles/openshift_node_certificates/README.md index f4215950f..fef2f0783 100644 --- a/roles/openshift_node_certificates/README.md +++ b/roles/openshift_node_certificates/README.md @@ -23,6 +23,7 @@ From this role: |-------------------------------------|-------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| | openshift_generated_configs_dir | `{{ openshift.common.config_base }}/generated-configs` | Directory in which per-node generated config directories will be created on the `openshift_ca_host`. | | openshift_node_cert_subdir | `node-{{ openshift.common.hostname }}` | Directory within `openshift_generated_configs_dir` where per-node certificates will be placed on the `openshift_ca_host`. | +| openshift_node_cert_expire_days | `730` (2 years) | Validity of the certificates in days. Works only with OpenShift version 1.5 (3.5) and later. | | openshift_node_config_dir | `{{ openshift.common.config_base }}/node` | Node configuration directory in which certificates will be deployed on nodes. | | openshift_node_generated_config_dir | `{{ openshift_generated_configs_dir }}/{{ openshift_node_cert_subdir }` | Full path to the per-node generated config directory. | diff --git a/roles/openshift_node_certificates/defaults/main.yml b/roles/openshift_node_certificates/defaults/main.yml new file mode 100644 index 000000000..70a38b844 --- /dev/null +++ b/roles/openshift_node_certificates/defaults/main.yml @@ -0,0 +1,2 @@ +--- +openshift_node_cert_expire_days: 730 diff --git a/roles/openshift_node_certificates/tasks/main.yml b/roles/openshift_node_certificates/tasks/main.yml index 4cb89aba2..9120915b2 100644 --- a/roles/openshift_node_certificates/tasks/main.yml +++ b/roles/openshift_node_certificates/tasks/main.yml @@ -66,6 +66,9 @@ --signer-key={{ openshift_ca_key }} --signer-serial={{ openshift_ca_serial }} --user=system:node:{{ hostvars[item].openshift.common.hostname }} + {% if openshift_version | oo_version_gte_3_5_or_1_5(openshift.common.deployment_type) | bool %} + --expire-days={{ openshift_node_cert_expire_days }} + {% endif %} args: creates: "{{ openshift_generated_configs_dir }}/node-{{ hostvars[item].openshift.common.hostname }}" with_items: "{{ hostvars @@ -79,6 +82,9 @@ {{ hostvars[openshift_ca_host].openshift.common.client_binary }} adm ca create-server-cert --cert={{ openshift_generated_configs_dir }}/node-{{ hostvars[item].openshift.common.hostname }}/server.crt --key={{ openshift_generated_configs_dir }}/node-{{ hostvars[item].openshift.common.hostname }}/server.key + {% if openshift_version | oo_version_gte_3_5_or_1_5(openshift.common.deployment_type) | bool %} + --expire-days={{ openshift_node_cert_expire_days }} + {% endif %} --overwrite=true --hostnames={{ hostvars[item].openshift.common.hostname }},{{ hostvars[item].openshift.common.public_hostname }},{{ hostvars[item].openshift.common.ip }},{{ hostvars[item].openshift.common.public_ip }} --signer-cert={{ openshift_ca_cert }} diff --git a/roles/openshift_repos/meta/main.yml b/roles/openshift_repos/meta/main.yml index cc18c453c..1b043863b 100644 --- a/roles/openshift_repos/meta/main.yml +++ b/roles/openshift_repos/meta/main.yml @@ -11,4 +11,5 @@ galaxy_info: - 7 categories: - cloud -dependencies: [] +dependencies: +- role: openshift_sanitize_inventory diff --git a/roles/openshift_repos/tasks/main.yaml b/roles/openshift_repos/tasks/main.yaml index ffb760bfe..84a0905cc 100644 --- a/roles/openshift_repos/tasks/main.yaml +++ b/roles/openshift_repos/tasks/main.yaml @@ -4,10 +4,6 @@ path: /run/ostree-booted register: ostree_booted -- assert: - that: openshift_deployment_type in known_openshift_deployment_types - msg: "openshift_deployment_type must be one of {{ known_openshift_deployment_types }}" - - block: - name: Ensure libselinux-python is installed package: name=libselinux-python state=present diff --git a/roles/openshift_sanitize_inventory/README.md b/roles/openshift_sanitize_inventory/README.md new file mode 100644 index 000000000..23f6b84fc --- /dev/null +++ b/roles/openshift_sanitize_inventory/README.md @@ -0,0 +1,37 @@ +OpenShift Inventory +=================== + +Provides a role to validate and normalize the variables the user has +provided. This role should run before pretty much everything else so that +this kind of logic only has to be in one place. However, complicated +business logic should usually be left to other roles. + +Requirements +------------ + +None + +Role Variables +-------------- + +None + +Dependencies +------------ + +None + +Example Playbook +---------------- + +TODO + +License +------- + +Apache License, Version 2.0 + +Author Information +------------------ + +OpenShift dev (dev@lists.openshift.redhat.com) diff --git a/roles/openshift_sanitize_inventory/meta/main.yml b/roles/openshift_sanitize_inventory/meta/main.yml new file mode 100644 index 000000000..f5b37186e --- /dev/null +++ b/roles/openshift_sanitize_inventory/meta/main.yml @@ -0,0 +1,15 @@ +--- +galaxy_info: + author: OpenShift dev + description: + company: Red Hat, Inc. + license: Apache License, Version 2.0 + min_ansible_version: 1.8 + platforms: + - name: EL + versions: + - 7 + categories: + - cloud + - system +dependencies: [] diff --git a/roles/openshift_sanitize_inventory/tasks/main.yml b/roles/openshift_sanitize_inventory/tasks/main.yml new file mode 100644 index 000000000..fc562c42c --- /dev/null +++ b/roles/openshift_sanitize_inventory/tasks/main.yml @@ -0,0 +1,28 @@ +--- +- name: Standardize on latest variable names + no_log: True # keep task description legible + set_fact: + # goal is to deprecate deployment_type in favor of openshift_deployment_type. + # both will be accepted for now, but code should refer to the new name. + # TODO: once this is well-documented, add deprecation notice if using old name. + deployment_type: "{{ openshift_deployment_type | default(deployment_type) | default | string }}" + openshift_deployment_type: "{{ openshift_deployment_type | default(deployment_type) | default | string }}" + +- name: Normalize openshift_release + no_log: True # keep task description legible + set_fact: + # Normalize release if provided, e.g. "v3.5" => "3.5" + # Currently this is not required to be defined for all installs, and the + # `openshift_version` role can generally figure out the specific version + # that gets installed (e.g. 3.5.0.1). So consider this the user's expressed + # intent (if any), not the authoritative version that will be installed. + openshift_release: "{{ openshift_release | string | regex_replace('^v', '') }}" + when: openshift_release is defined + +- name: Ensure a valid deployment type has been given. + # this variable is required; complain early and clearly if it is invalid. + when: openshift_deployment_type not in known_openshift_deployment_types + fail: + msg: |- + Please set openshift_deployment_type to one of: + {{ known_openshift_deployment_types | join(', ') }} diff --git a/roles/openshift_repos/vars/main.yml b/roles/openshift_sanitize_inventory/vars/main.yml index da48e42c1..da48e42c1 100644 --- a/roles/openshift_repos/vars/main.yml +++ b/roles/openshift_sanitize_inventory/vars/main.yml diff --git a/roles/openshift_version/tasks/main.yml b/roles/openshift_version/tasks/main.yml index 0f2a660a7..35953b744 100644 --- a/roles/openshift_version/tasks/main.yml +++ b/roles/openshift_version/tasks/main.yml @@ -13,14 +13,6 @@ # Normalize some values that we need in a certain format that might be confusing: - set_fact: - openshift_release: "{{ openshift_release[1:] }}" - when: openshift_release is defined and openshift_release[0] == 'v' - -- set_fact: - openshift_release: "{{ openshift_release | string }}" - when: openshift_release is defined - -- set_fact: openshift_image_tag: "{{ 'v' + openshift_image_tag }}" when: openshift_image_tag is defined and openshift_image_tag[0] != 'v' and openshift_image_tag != 'latest' diff --git a/roles/openshift_version/tasks/set_version_rpm.yml b/roles/openshift_version/tasks/set_version_rpm.yml index 7fa74e24f..0c2ef4bb7 100644 --- a/roles/openshift_version/tasks/set_version_rpm.yml +++ b/roles/openshift_version/tasks/set_version_rpm.yml @@ -5,14 +5,42 @@ openshift_version: "{{ openshift_pkg_version[1:].split('-')[0] }}" when: openshift_pkg_version is defined and openshift_version is not defined +# if {{ openshift.common.service_type}}-excluder is enabled, +# the repoquery for {{ openshift.common.service_type}} will not work. +# Thus, create a temporary yum,conf file where exclude= is set to an empty list +- name: Create temporary yum.conf file + command: mktemp -d /tmp/yum.conf.XXXXXX + register: yum_conf_temp_file_result + +- set_fact: + yum_conf_temp_file: "{{yum_conf_temp_file_result.stdout}}/yum.conf" + +- name: Copy yum.conf into the temporary file + copy: + src: /etc/yum.conf + dest: "{{ yum_conf_temp_file }}" + remote_src: True + +- name: Clear the exclude= list in the temporary yum.conf + lineinfile: + # since ansible 2.3 s/dest/path + dest: "{{ yum_conf_temp_file }}" + regexp: '^exclude=' + line: 'exclude=' + - name: Gather common package version command: > - {{ repoquery_cmd }} --qf '%{version}' "{{ openshift.common.service_type}}" + {{ repoquery_cmd }} --config "{{ yum_conf_temp_file }}" --qf '%{version}' "{{ openshift.common.service_type}}" register: common_version failed_when: false changed_when: false when: openshift_version is not defined +- name: Delete the temporary yum.conf + file: + path: "{{ yum_conf_temp_file_result.stdout }}" + state: absent + - set_fact: openshift_version: "{{ common_version.stdout | default('0.0', True) }}" when: openshift_version is not defined |