From ee21083985c5b0a70b692e93d8493452eb0eb5d6 Mon Sep 17 00:00:00 2001 From: Fabian von Feilitzsch Date: Mon, 17 Aug 2020 15:41:37 -0400 Subject: [PATCH] Add Molecule tests (#7) * Initial port to molecule * Get molecule tests to run * Draw the rest of the owl * use local dir for storing collection during CI run * Add dockerfile, install community.kubernetes collection before sanity check * Add clean step to Makefile * Get sanity tests working * Update sanity test to use makefile * Add ignores for 2.11 so devel passes * Update description * Code review * Add OWNERS file for prow --- .github/workflows/ansible-test.yml | 2 +- .gitignore | 3 + .yamllint | 33 ++ Makefile | 18 + OWNERS | 6 + README.md | 19 +- ci/Dockerfile | 14 + galaxy.yml | 4 +- .../okd => molecule/default}/README.md | 0 molecule/default/converge.yml | 59 +++ molecule/default/destroy.yml | 6 + .../default}/files/crd-resource.yml | 0 .../default}/files/kuard-extra-property.yml | 2 +- .../default}/files/kuard-invalid-type.yml | 2 +- .../default}/files/setup-crd.yml | 0 molecule/default/molecule.yml | 38 ++ molecule/default/prepare.yml | 17 + .../default}/tasks/validate_installed.yml | 51 ++- .../default}/tasks/validate_not_installed.yml | 2 +- molecule/default/verify.yml | 26 ++ plugins/modules/k8s.py | 336 ++++++++++++++++++ .../integration/targets/okd/defaults/main.yml | 2 - .../integration/targets/okd/handlers/main.yml | 6 - .../integration/targets/okd/library/README.md | 3 - .../targets/okd/library/test_tempfile.py | 121 ------- tests/integration/targets/okd/meta/main.yml | 2 - tests/integration/targets/okd/tasks/main.yml | 50 --- .../okd/tasks/older_openshift_fail.yml | 71 ---- .../targets/okd/tasks/openshift.yml | 62 ---- .../okd/tasks/setup_remote_tmp_dir.yml | 12 - tests/sanity/ignore-2.10.txt | 2 + tests/sanity/ignore-2.11.txt | 2 + tests/sanity/ignore-2.9.txt | 1 + 33 files changed, 603 insertions(+), 369 deletions(-) create mode 100644 .yamllint create mode 100644 Makefile create mode 100644 OWNERS create mode 100644 ci/Dockerfile rename {tests/integration/targets/okd => molecule/default}/README.md (100%) create mode 100644 molecule/default/converge.yml create mode 100644 molecule/default/destroy.yml rename {tests/integration/targets/okd => molecule/default}/files/crd-resource.yml (100%) rename {tests/integration/targets/okd => molecule/default}/files/kuard-extra-property.yml (91%) rename {tests/integration/targets/okd => molecule/default}/files/kuard-invalid-type.yml (91%) rename {tests/integration/targets/okd => molecule/default}/files/setup-crd.yml (100%) create mode 100644 molecule/default/molecule.yml create mode 100644 molecule/default/prepare.yml rename {tests/integration/targets/okd => molecule/default}/tasks/validate_installed.yml (69%) rename {tests/integration/targets/okd => molecule/default}/tasks/validate_not_installed.yml (95%) create mode 100644 molecule/default/verify.yml create mode 100644 plugins/modules/k8s.py delete mode 100644 tests/integration/targets/okd/defaults/main.yml delete mode 100644 tests/integration/targets/okd/handlers/main.yml delete mode 100644 tests/integration/targets/okd/library/README.md delete mode 100644 tests/integration/targets/okd/library/test_tempfile.py delete mode 100644 tests/integration/targets/okd/meta/main.yml delete mode 100644 tests/integration/targets/okd/tasks/main.yml delete mode 100644 tests/integration/targets/okd/tasks/older_openshift_fail.yml delete mode 100644 tests/integration/targets/okd/tasks/openshift.yml delete mode 100644 tests/integration/targets/okd/tasks/setup_remote_tmp_dir.yml create mode 100644 tests/sanity/ignore-2.10.txt create mode 100644 tests/sanity/ignore-2.11.txt create mode 100644 tests/sanity/ignore-2.9.txt diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index 43a8e93..298eeb3 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -51,7 +51,7 @@ jobs: # The docker container has all the pinned dependencies that are required. # Explicity specify the version of Python we want to test - name: Run sanity tests - run: ansible-test sanity --docker -v --color --python ${{ matrix.python }} + run: make test-sanity TEST_ARGS='--python ${{ matrix.python }}' working-directory: ./ansible_collections/community/okd ### diff --git a/.gitignore b/.gitignore index f97b787..3252ef6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ *.log __pycache__/ +# temp collection path +# Can't ignore this directory for now, as the files have to be viewable by git in order to be sanity-checked +# ansible_collections/ # Galaxy artifacts. *.tar.gz diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..8827676 --- /dev/null +++ b/.yamllint @@ -0,0 +1,33 @@ +--- +# Based on ansible-lint config +extends: default + +rules: + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + colons: + max-spaces-after: -1 + level: error + commas: + max-spaces-after: -1 + level: error + comments: disable + comments-indentation: disable + document-start: disable + empty-lines: + max: 3 + level: error + hyphens: + level: error + indentation: disable + key-duplicates: enable + line-length: disable + new-line-at-end-of-file: disable + new-lines: + type: unix + trailing-spaces: disable + truthy: disable diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7c5e0b7 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +# Also needs to be updated in galaxy.yml +VERSION = 0.1.0 + +clean: + rm -f community-okd-${VERSION}.tar.gz + rm -rf ansible_collections + +build: clean + ansible-galaxy collection build + +install: build + ansible-galaxy collection install -p ansible_collections community-okd-${VERSION}.tar.gz + +test-sanity: install + cd ansible_collections/community/okd && ansible-test sanity -v --docker --color $(TEST_ARGS) + +test-integration: install + molecule test diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..094c3d0 --- /dev/null +++ b/OWNERS @@ -0,0 +1,6 @@ +approvers: + - geerlingguy + - fabianvf +reviewers: + - geerlingguy + - fabianvf diff --git a/README.md b/README.md index f6e7d45..81d4873 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Click on the name of a plugin or module to view that content's documentation: - **Inventory Source**: - [openshift](https://docs.ansible.com/ansible/2.10/collections/community/kubernetes/openshift_inventory.html) + - **Modules**: + - [k8s](https://docs.ansible.com/ansible/latest/modules/k8s_module.html) ## Installation and Usage @@ -54,14 +56,19 @@ For documentation on how to use individual plugins included in this collection, If you want to develop new content for this collection or improve what's already here, the easiest way to work on the collection is to clone it into one of the configured [`COLLECTIONS_PATHS`](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths), and work on it there. -### Testing with `ansible-test` +The `tests` directory contains configuration for running sanity tests using [`ansible-test`](https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html). -The `tests` directory contains configuration for running sanity and integration tests using [`ansible-test`](https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html). +You can run the `ansible-test` sanity tests with the command: -You can run the collection's test suites with the commands: + make test-sanity - ansible-test sanity --docker -v --color - ansible-test integration --docker -v --color +The `molecule` directory contains configuration for running integration tests using [`molecule`](https://molecule.readthedocs.io/). + +You can run the `molecule` integration tests with the command: + + make test-integration + +These commands will create a directory called `ansible_collections` which should not be committed or added to the `.gitignore` (Tracking issue: https://github.com/ansible/ansible/issues/68499) ## Publishing New Versions @@ -69,7 +76,7 @@ The current process for publishing new versions of the OKD Collection is manual, 1. Ensure you're running Ansible from devel, so the [`build_ignore` key](https://github.com/ansible/ansible/issues/67130) in `galaxy.yml` is used. 1. Run `git clean -x -d -f` in this repository's directory to clean out any extra files which should not be included. - 1. Update `galaxy.yml` and this README's `requirements.yml` example with the new `version` for the collection. + 1. Update `galaxy.yml`, the `Makefile`, and this README's `requirements.yml` example with the new `version` for the collection. 1. Update the CHANGELOG: 1. Make sure you have [`antsibull-changelog`](https://pypi.org/project/antsibull-changelog/) installed. 1. Make sure there are fragments for all known changes in `changelogs/fragments`. diff --git a/ci/Dockerfile b/ci/Dockerfile new file mode 100644 index 0000000..18f88db --- /dev/null +++ b/ci/Dockerfile @@ -0,0 +1,14 @@ +FROM registry.access.redhat.com/ubi8/ubi + +RUN yum install -y make python3 python3-devel python3-pip python3-setuptools \ + && pip3 install --upgrade setuptools pip \ + && pip3 install \ + openshift \ + ansible \ + molecule + +WORKDIR /src + +COPY . /src + +USER 1001 diff --git a/galaxy.yml b/galaxy.yml index 9bf96fe..54613cb 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -23,7 +23,5 @@ tags: - openshift - okd - cluster +# Also needs to be updated in the Makefile version: 0.1.0 -build_ignore: - - .DS_Store - - '*.tar.gz' diff --git a/tests/integration/targets/okd/README.md b/molecule/default/README.md similarity index 100% rename from tests/integration/targets/okd/README.md rename to molecule/default/README.md diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml new file mode 100644 index 0000000..8966b82 --- /dev/null +++ b/molecule/default/converge.yml @@ -0,0 +1,59 @@ +--- +- name: Converge + hosts: localhost + connection: local + gather_facts: no + vars: + ansible_python_interpreter: '{{ virtualenv_interpreter }}' + tasks: + # OpenShift Resources + - name: Create a project + community.okd.k8s: + name: testing + kind: Project + api_version: project.openshift.io/v1 + apply: no + register: output + + - name: show output + debug: + var: output + + - name: Create deployment config + community.okd.k8s: + state: present + inline: &dc + apiVersion: v1 + kind: DeploymentConfig + metadata: + name: elastic + labels: + app: galaxy + service: elastic + namespace: testing + spec: + template: + metadata: + labels: + app: galaxy + service: elastic + spec: + containers: + - name: elastic + volumeMounts: + - mountPath: /usr/share/elasticsearch/data + name: elastic-volume + command: ['elasticsearch'] + image: 'ansible/galaxy-elasticsearch:2.4.6' + volumes: + - name: elastic-volume + persistentVolumeClaim: + claimName: elastic-volume + replicas: 1 + strategy: + type: Rolling + register: output + + - name: Show output + debug: + var: output diff --git a/molecule/default/destroy.yml b/molecule/default/destroy.yml new file mode 100644 index 0000000..0bd583e --- /dev/null +++ b/molecule/default/destroy.yml @@ -0,0 +1,6 @@ +--- +- name: Destroy + hosts: localhost + connection: local + gather_facts: no + tasks: [] diff --git a/tests/integration/targets/okd/files/crd-resource.yml b/molecule/default/files/crd-resource.yml similarity index 100% rename from tests/integration/targets/okd/files/crd-resource.yml rename to molecule/default/files/crd-resource.yml diff --git a/tests/integration/targets/okd/files/kuard-extra-property.yml b/molecule/default/files/kuard-extra-property.yml similarity index 91% rename from tests/integration/targets/okd/files/kuard-extra-property.yml rename to molecule/default/files/kuard-extra-property.yml index bed92bc..1da160b 100644 --- a/tests/integration/targets/okd/files/kuard-extra-property.yml +++ b/molecule/default/files/kuard-extra-property.yml @@ -1,5 +1,5 @@ --- -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: labels: diff --git a/tests/integration/targets/okd/files/kuard-invalid-type.yml b/molecule/default/files/kuard-invalid-type.yml similarity index 91% rename from tests/integration/targets/okd/files/kuard-invalid-type.yml rename to molecule/default/files/kuard-invalid-type.yml index 72505f8..6ff8018 100644 --- a/tests/integration/targets/okd/files/kuard-invalid-type.yml +++ b/molecule/default/files/kuard-invalid-type.yml @@ -1,5 +1,5 @@ --- -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: labels: diff --git a/tests/integration/targets/okd/files/setup-crd.yml b/molecule/default/files/setup-crd.yml similarity index 100% rename from tests/integration/targets/okd/files/setup-crd.yml rename to molecule/default/files/setup-crd.yml diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 0000000..7600539 --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,38 @@ +--- +dependency: + name: galaxy +driver: + name: delegated +platforms: + - name: cluster + groups: + - k8s +provisioner: + name: ansible + lint: | + set -e + ansible-lint + inventory: + host_vars: + localhost: + virtualenv: ${MOLECULE_EPHEMERAL_DIRECTORY}/virtualenv + virtualenv_command: '{{ ansible_playbook_python }} -m virtualenv' + virtualenv_interpreter: '{{ virtualenv }}/bin/python' + playbook_namespace: molecule-tests + env: + ANSIBLE_FORCE_COLOR: 'true' + ANSIBLE_COLLECTIONS_PATHS: ${MOLECULE_PROJECT_DIRECTORY} +verifier: + name: ansible + lint: | + set -e + ansible-lint +scenario: + name: default + test_sequence: + - lint + - syntax + - prepare + - converge + - idempotence + - verify diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml new file mode 100644 index 0000000..fb9e992 --- /dev/null +++ b/molecule/default/prepare.yml @@ -0,0 +1,17 @@ +--- +- name: Prepare + hosts: localhost + connection: local + gather_facts: no + + tasks: + - pip: + name: virtualenv + + - pip: + name: + - openshift>=0.9.2 + - coverage + virtualenv: "{{ virtualenv }}" + virtualenv_command: "{{ virtualenv_command }}" + virtualenv_site_packages: no diff --git a/tests/integration/targets/okd/tasks/validate_installed.yml b/molecule/default/tasks/validate_installed.yml similarity index 69% rename from tests/integration/targets/okd/tasks/validate_installed.yml rename to molecule/default/tasks/validate_installed.yml index 6f5956d..daf82e6 100644 --- a/tests/integration/targets/okd/tasks/validate_installed.yml +++ b/molecule/default/tasks/validate_installed.yml @@ -1,16 +1,13 @@ --- - block: - - name: Create a namespace - community.kubernetes.k8s: + - name: Create a project + community.okd.k8s: name: "{{ playbook_namespace }}" - kind: Namespace - - - copy: - src: files - dest: "{{ remote_tmp_dir }}" + kind: Project + api_version: project.openshift.io/v1 - name: incredibly simple ConfigMap - community.kubernetes.k8s: + community.okd.k8s: definition: apiVersion: v1 kind: ConfigMap @@ -27,16 +24,16 @@ - k8s_with_validate is successful - name: extra property does not fail without strict - community.kubernetes.k8s: - src: "{{ remote_tmp_dir }}/files/kuard-extra-property.yml" + community.okd.k8s: + src: "files/kuard-extra-property.yml" namespace: "{{ playbook_namespace }}" validate: fail_on_error: yes strict: no - name: extra property fails with strict - community.kubernetes.k8s: - src: "{{ remote_tmp_dir }}/files/kuard-extra-property.yml" + community.okd.k8s: + src: "files/kuard-extra-property.yml" namespace: "{{ playbook_namespace }}" validate: fail_on_error: yes @@ -50,8 +47,8 @@ - extra_property is failed - name: invalid type fails at validation stage - community.kubernetes.k8s: - src: "{{ remote_tmp_dir }}/files/kuard-invalid-type.yml" + community.okd.k8s: + src: "files/kuard-invalid-type.yml" namespace: "{{ playbook_namespace }}" validate: fail_on_error: yes @@ -65,8 +62,8 @@ - invalid_type is failed - name: invalid type fails with warnings when fail_on_error is False - community.kubernetes.k8s: - src: "{{ remote_tmp_dir }}/files/kuard-invalid-type.yml" + community.okd.k8s: + src: "files/kuard-invalid-type.yml" namespace: "{{ playbook_namespace }}" validate: fail_on_error: no @@ -80,16 +77,16 @@ - invalid_type_no_fail is failed - name: setup custom resource definition - community.kubernetes.k8s: - src: "{{ remote_tmp_dir }}/files/setup-crd.yml" + community.okd.k8s: + src: "files/setup-crd.yml" - name: wait a few seconds pause: seconds: 5 - name: add custom resource definition - community.kubernetes.k8s: - src: "{{ remote_tmp_dir }}/files/crd-resource.yml" + community.okd.k8s: + src: "files/crd-resource.yml" namespace: "{{ playbook_namespace }}" validate: fail_on_error: yes @@ -104,23 +101,23 @@ always: - name: remove custom resource - community.kubernetes.k8s: - definition: "{{ lookup('file', role_path + '/files/crd-resource.yml') }}" + community.okd.k8s: + definition: "{{ lookup('file', 'files/crd-resource.yml') }}" namespace: "{{ playbook_namespace }}" state: absent ignore_errors: yes - name: remove custom resource definitions - community.kubernetes.k8s: - definition: "{{ lookup('file', role_path + '/files/setup-crd.yml') }}" + community.okd.k8s: + definition: "{{ lookup('file', 'files/setup-crd.yml') }}" state: absent - name: Delete namespace - community.kubernetes.k8s: + community.okd.k8s: state: absent definition: - - kind: Namespace - apiVersion: v1 + - kind: Project + apiVersion: project.openshift.io/v1 metadata: name: "{{ playbook_namespace }}" ignore_errors: yes diff --git a/tests/integration/targets/okd/tasks/validate_not_installed.yml b/molecule/default/tasks/validate_not_installed.yml similarity index 95% rename from tests/integration/targets/okd/tasks/validate_not_installed.yml rename to molecule/default/tasks/validate_not_installed.yml index 91ddf9f..44bee7b 100644 --- a/tests/integration/targets/okd/tasks/validate_not_installed.yml +++ b/molecule/default/tasks/validate_not_installed.yml @@ -6,7 +6,7 @@ # - kubernetes # - kubernetes-validate -- community.kubernetes.k8s: +- community.okd.k8s: definition: apiVersion: v1 kind: ConfigMap diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml new file mode 100644 index 0000000..799ebab --- /dev/null +++ b/molecule/default/verify.yml @@ -0,0 +1,26 @@ +--- +- name: Verify + hosts: localhost + connection: local + gather_facts: no + vars: + ansible_python_interpreter: '{{ virtualenv_interpreter }}' + + tasks: + + - pip: + name: kubernetes-validate==1.12.0 + virtualenv: "{{ virtualenv }}" + virtualenv_command: "{{ virtualenv_command }}" + virtualenv_site_packages: no + + - import_tasks: tasks/validate_installed.yml + + - pip: + name: kubernetes-validate + state: absent + virtualenv: "{{ virtualenv }}" + virtualenv_command: "{{ virtualenv_command }}" + virtualenv_site_packages: no + + - import_tasks: tasks/validate_not_installed.yml diff --git a/plugins/modules/k8s.py b/plugins/modules/k8s.py new file mode 100644 index 0000000..2f60511 --- /dev/null +++ b/plugins/modules/k8s.py @@ -0,0 +1,336 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018, Chris Houseknecht <@chouseknecht> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' + +module: k8s + +short_description: Manage OpenShift objects + +author: + - "Chris Houseknecht (@chouseknecht)" + - "Fabian von Feilitzsch (@fabianvf)" + +description: + - Use the OpenShift Python client to perform CRUD operations on K8s objects. + - Pass the object definition from a source file or inline. See examples for reading + files and using Jinja templates or vault-encrypted files. + - Access to the full range of K8s APIs. + - Use the M(k8s_info) module to obtain a list of items about an object of type C(kind) + - Authenticate using either a config file, certificates, password or token. + - Supports check mode. + - Optimized for OKD/OpenShift Kubernetes flavors + +extends_documentation_fragment: + - community.kubernetes.k8s_state_options + - community.kubernetes.k8s_name_options + - community.kubernetes.k8s_resource_options + - community.kubernetes.k8s_auth_options + +notes: + - If your OpenShift Python library is not 0.9.0 or newer and you are trying to + remove an item from an associative array/dictionary, for example a label or + an annotation, you will need to explicitly set the value of the item to be + removed to `null`. Simply deleting the entry in the dictionary will not + remove it from openshift or kubernetes. + +options: + merge_type: + description: + - Whether to override the default patch merge approach with a specific type. By default, the strategic + merge will typically be used. + - For example, Custom Resource Definitions typically aren't updatable by the usual strategic merge. You may + want to use C(merge) if you see "strategic merge patch format is not supported" + - See U(https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment) + - Requires openshift >= 0.6.2 + - If more than one merge_type is given, the merge_types will be tried in order + - If openshift >= 0.6.2, this defaults to C(['strategic-merge', 'merge']), which is ideal for using the same parameters + on resource kinds that combine Custom Resources and built-in resources. For openshift < 0.6.2, the default + is simply C(strategic-merge). + - mutually exclusive with C(apply) + choices: + - json + - merge + - strategic-merge + type: list + elements: str + wait: + description: + - Whether to wait for certain resource kinds to end up in the desired state. By default the module exits once Kubernetes has + received the request + - Implemented for C(state=present) for C(Deployment), C(DaemonSet) and C(Pod), and for C(state=absent) for all resource kinds. + - For resource kinds without an implementation, C(wait) returns immediately unless C(wait_condition) is set. + default: no + type: bool + wait_sleep: + description: + - Number of seconds to sleep between checks. + default: 5 + type: int + wait_timeout: + description: + - How long in seconds to wait for the resource to end up in the desired state. Ignored if C(wait) is not set. + default: 120 + type: int + wait_condition: + description: + - Specifies a custom condition on the status to wait for. Ignored if C(wait) is not set or is set to False. + suboptions: + type: + type: str + description: + - The type of condition to wait for. For example, the C(Pod) resource will set the C(Ready) condition (among others) + - Required if you are specifying a C(wait_condition). If left empty, the C(wait_condition) field will be ignored. + - The possible types for a condition are specific to each resource type in Kubernetes. See the API documentation of the status field + for a given resource to see possible choices. + status: + type: str + description: + - The value of the status field in your desired condition. + - For example, if a C(Deployment) is paused, the C(Progressing) C(type) will have the C(Unknown) status. + choices: + - True + - False + - Unknown + default: "True" + reason: + type: str + description: + - The value of the reason field in your desired condition + - For example, if a C(Deployment) is paused, The C(Progressing) C(type) will have the C(DeploymentPaused) reason. + - The possible reasons in a condition are specific to each resource type in Kubernetes. See the API documentation of the status field + for a given resource to see possible choices. + type: dict + validate: + description: + - how (if at all) to validate the resource definition against the kubernetes schema. + Requires the kubernetes-validate python module + suboptions: + fail_on_error: + description: whether to fail on validation errors. + type: bool + version: + description: version of Kubernetes to validate against. defaults to Kubernetes server version + type: str + strict: + description: whether to fail when passing unexpected properties + default: True + type: bool + type: dict + append_hash: + description: + - Whether to append a hash to a resource name for immutability purposes + - Applies only to ConfigMap and Secret resources + - The parameter will be silently ignored for other resource kinds + - The full definition of an object is needed to generate the hash - this means that deleting an object created with append_hash + will only work if the same object is passed with state=absent (alternatively, just use state=absent with the name including + the generated hash and append_hash=no) + type: bool + apply: + description: + - C(apply) compares the desired resource definition with the previously supplied resource definition, + ignoring properties that are automatically generated + - C(apply) works better with Services than 'force=yes' + - mutually exclusive with C(merge_type) + type: bool + +requirements: + - "python >= 2.7" + - "openshift >= 0.6" + - "PyYAML >= 3.11" +''' + +EXAMPLES = ''' +- name: Create a k8s namespace + community.okd.k8s: + name: testing + api_version: v1 + kind: Namespace + state: present + +- name: Create a Service object from an inline definition + community.okd.k8s: + state: present + definition: + apiVersion: v1 + kind: Service + metadata: + name: web + namespace: testing + labels: + app: galaxy + service: web + spec: + selector: + app: galaxy + service: web + ports: + - protocol: TCP + targetPort: 8000 + name: port-8000-tcp + port: 8000 + +- name: Remove an existing Service object + community.okd.k8s: + state: absent + api_version: v1 + kind: Service + namespace: testing + name: web + +# Passing the object definition from a file + +- name: Create a Deployment by reading the definition from a local file + community.okd.k8s: + state: present + src: /testing/deployment.yml + +- name: >- + Read definition file from the Ansible controller file system. + If the definition file has been encrypted with Ansible Vault it will automatically be decrypted. + community.okd.k8s: + state: present + definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}" + +- name: Read definition file from the Ansible controller file system after Jinja templating + community.okd.k8s: + state: present + definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}" + +- name: fail on validation errors + community.okd.k8s: + state: present + definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}" + validate: + fail_on_error: yes + +- name: warn on validation errors, check for unexpected properties + community.okd.k8s: + state: present + definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}" + validate: + fail_on_error: no + strict: yes +''' + +RETURN = ''' +result: + description: + - The created, patched, or otherwise present object. Will be empty in the case of a deletion. + returned: success + type: complex + contains: + api_version: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: complex + spec: + description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). + returned: success + type: complex + status: + description: Current status details for the object. + returned: success + type: complex + items: + description: Returned only when multiple yaml documents are passed to src or resource_definition + returned: when resource_definition or src contains list of objects + type: list + duration: + description: elapsed time of task in seconds + returned: when C(wait) is true + type: int + sample: 48 +''' + +import traceback + +from ansible.module_utils._text import to_native + +try: + from ansible_collections.community.kubernetes.plugins.module_utils.raw import KubernetesRawModule + HAS_KUBERNETES_COLLECTION = True +except ImportError as e: + HAS_KUBERNETES_COLLECTION = False + k8s_collection_import_exception = e + K8S_COLLECTION_ERROR = traceback.format_exc() + from ansible.module_utils.basic import AnsibleModule as KubernetesRawModule + +try: + from openshift.dynamic.exceptions import DynamicApiError, NotFoundError, ForbiddenError +except ImportError: + # Exceptions handled in common + pass + + +class OKDRawModule(KubernetesRawModule): + + def __init__(self): + if not HAS_KUBERNETES_COLLECTION: + self.fail_json( + msg="The community.kubernetes collection must be installed", + exception=K8S_COLLECTION_ERROR, + error=to_native(k8s_collection_import_exception) + ) + super(OKDRawModule, self).__init__() + + def perform_action(self, resource, definition): + state = self.params.get('state', None) + name = definition['metadata'].get('name') + namespace = definition['metadata'].get('namespace') + + if definition['kind'] in ['Project', 'ProjectRequest'] and state != 'absent': + try: + resource.get(name, namespace) + except (NotFoundError, ForbiddenError): + return self.create_project_request(definition) + except DynamicApiError as exc: + self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc.body), + error=exc.status, status=exc.status, reason=exc.reason) + + return super(OKDRawModule, self).perform_action(resource, definition) + + def create_project_request(self, definition): + definition['kind'] = 'ProjectRequest' + result = {'changed': False, 'result': {}} + resource = self.find_resource('ProjectRequest', definition['apiVersion'], fail=True) + if not self.check_mode: + try: + k8s_obj = resource.create(definition) + result['result'] = k8s_obj.to_dict() + except DynamicApiError as exc: + self.fail_json(msg="Failed to create object: {0}".format(exc.body), + error=exc.status, status=exc.status, reason=exc.reason) + result['changed'] = True + result['method'] = 'create' + return result + + +def main(): + OKDRawModule().execute_module() + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/okd/defaults/main.yml b/tests/integration/targets/okd/defaults/main.yml deleted file mode 100644 index e46ca26..0000000 --- a/tests/integration/targets/okd/defaults/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -k8s_openshift: true diff --git a/tests/integration/targets/okd/handlers/main.yml b/tests/integration/targets/okd/handlers/main.yml deleted file mode 100644 index efb5408..0000000 --- a/tests/integration/targets/okd/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: delete temporary directory - file: - path: "{{ remote_tmp_dir }}" - state: absent - no_log: yes diff --git a/tests/integration/targets/okd/library/README.md b/tests/integration/targets/okd/library/README.md deleted file mode 100644 index ac31229..0000000 --- a/tests/integration/targets/okd/library/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# README - -The `test_tempfile.py` module added here is only used for the `setup_remote_tmp_dir.yml` temporary directory setup task. It is a clone of the `tempfile.py` community-supported Ansible module, and has to be included with the tests here because it is not available in the `ansible-base` distribution against which this collection is tested. diff --git a/tests/integration/targets/okd/library/test_tempfile.py b/tests/integration/targets/okd/library/test_tempfile.py deleted file mode 100644 index c89f5a3..0000000 --- a/tests/integration/targets/okd/library/test_tempfile.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright: (c) 2016, Krzysztof Magosa -# Copyright: (c) 2017, Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -DOCUMENTATION = ''' ---- -module: test_tempfile - -short_description: Creates temporary files and directories - -description: - - The C(test_tempfile) module creates temporary files and directories. C(mktemp) command takes different parameters on various systems, this module helps - to avoid troubles related to that. Files/directories created by module are accessible only by creator. In case you need to make them world-accessible - you need to use M(ansible.builtin.file) module. - - For Windows targets, use the M(ansible.builtin.win_tempfile) module instead. - -options: - state: - description: - - Whether to create file or directory. - type: str - choices: [ directory, file ] - default: file - path: - description: - - Location where temporary file or directory should be created. - - If path is not specified, the default system temporary directory will be used. - type: path - prefix: - description: - - Prefix of file/directory name created by module. - type: str - default: ansible. - suffix: - description: - - Suffix of file/directory name created by module. - type: str - default: "" - -seealso: -- module: file -- module: win_tempfile - -author: - - Krzysztof Magosa (@krzysztof-magosa) -''' - -EXAMPLES = """ -- name: create temporary build directory - test_tempfile: - state: directory - suffix: build - -- name: create temporary file - test_tempfile: - state: file - suffix: temp - register: tempfile_1 - -- name: use the registered var and the file module to remove the temporary file - file: - path: "{{ tempfile_1.path }}" - state: absent - when: tempfile_1.path is defined -""" - -RETURN = ''' -path: - description: Path to created file or directory - returned: success - type: str - sample: "/tmp/ansible.bMlvdk" -''' - -from os import close -from tempfile import mkstemp, mkdtemp -from traceback import format_exc - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native - - -def main(): - module = AnsibleModule( - argument_spec=dict( - state=dict(type='str', default='file', choices=['file', 'directory']), - path=dict(type='path'), - prefix=dict(type='str', default='ansible.'), - suffix=dict(type='str', default=''), - ), - ) - - try: - if module.params['state'] == 'file': - handle, path = mkstemp( - prefix=module.params['prefix'], - suffix=module.params['suffix'], - dir=module.params['path'], - ) - close(handle) - elif module.params['state'] == 'directory': - path = mkdtemp( - prefix=module.params['prefix'], - suffix=module.params['suffix'], - dir=module.params['path'], - ) - - module.exit_json(changed=True, path=path) - except Exception as e: - module.fail_json(msg=to_native(e), exception=format_exc()) - - -if __name__ == '__main__': - main() diff --git a/tests/integration/targets/okd/meta/main.yml b/tests/integration/targets/okd/meta/main.yml deleted file mode 100644 index 23d65c7..0000000 --- a/tests/integration/targets/okd/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -dependencies: [] diff --git a/tests/integration/targets/okd/tasks/main.yml b/tests/integration/targets/okd/tasks/main.yml deleted file mode 100644 index 1a97171..0000000 --- a/tests/integration/targets/okd/tasks/main.yml +++ /dev/null @@ -1,50 +0,0 @@ ---- -- include_tasks: setup_remote_tmp_dir.yml - -- set_fact: - virtualenv: "{{ remote_tmp_dir }}/virtualenv" - virtualenv_command: "{{ ansible_python_interpreter }} -m virtualenv" - -- set_fact: - virtualenv_interpreter: "{{ virtualenv }}/bin/python" - -- pip: - name: virtualenv - -# Test graceful failure for older versions of openshift - -- pip: - name: - - openshift==0.6.0 - - kubernetes==6.0.0 - - coverage - virtualenv: "{{ virtualenv }}" - virtualenv_command: "{{ virtualenv_command }}" - virtualenv_site_packages: no - -- include_tasks: older_openshift_fail.yml - vars: - ansible_python_interpreter: "{{ virtualenv_interpreter }}" - playbook_namespace: ansible-test-k8s-older-openshift - -- file: - path: "{{ virtualenv }}" - state: absent - no_log: yes - -# Test openshift - -- debug: - var: k8s_openshift - -- pip: - name: - - kubernetes-validate==1.12.0 - - openshift>=0.9.2 - - coverage - virtualenv: "{{ virtualenv }}" - virtualenv_command: "{{ virtualenv_command }}" - virtualenv_site_packages: no - -- include: openshift.yml - when: k8s_openshift | bool diff --git a/tests/integration/targets/okd/tasks/older_openshift_fail.yml b/tests/integration/targets/okd/tasks/older_openshift_fail.yml deleted file mode 100644 index aecf978..0000000 --- a/tests/integration/targets/okd/tasks/older_openshift_fail.yml +++ /dev/null @@ -1,71 +0,0 @@ ---- -# TODO: Not available in ansible-base -# - python_requirements_info: -# dependencies: -# - openshift==0.6.0 -# - kubernetes==6.0.0 - -# append_hash -- name: use append_hash with ConfigMap - community.kubernetes.k8s: - definition: - metadata: - name: config-map-test - namespace: "{{ playbook_namespace }}" - apiVersion: v1 - kind: ConfigMap - data: - hello: world - append_hash: yes - ignore_errors: yes - register: k8s_append_hash - -- name: assert that append_hash fails gracefully - assert: - that: - - k8s_append_hash is failed - - "'Failed to import the required Python library (openshift >= 0.7.2)' in k8s_append_hash.msg" - - "'. This is required for append_hash.' in k8s_append_hash.msg" - -# validate -- name: attempt to use validate with older openshift - community.kubernetes.k8s: - definition: - metadata: - name: config-map-test - namespace: "{{ playbook_namespace }}" - apiVersion: v1 - kind: ConfigMap - data: - hello: world - validate: - fail_on_error: yes - ignore_errors: yes - register: k8s_validate - -- name: assert that validate fails gracefully - assert: - that: - - k8s_validate is failed - - "k8s_validate.msg == 'openshift >= 0.8.0 is required for validate'" - -# apply -- name: attempt to use apply with older openshift - community.kubernetes.k8s: - definition: - metadata: - name: config-map-test - namespace: "{{ playbook_namespace }}" - apiVersion: v1 - kind: ConfigMap - data: - hello: world - apply: yes - ignore_errors: yes - register: k8s_apply - -- name: assert that apply fails gracefully - assert: - that: - - k8s_apply is failed - - "k8s_apply.msg.startswith('Failed to import the required Python library (openshift >= 0.9.2)')" diff --git a/tests/integration/targets/okd/tasks/openshift.yml b/tests/integration/targets/okd/tasks/openshift.yml deleted file mode 100644 index 54949c1..0000000 --- a/tests/integration/targets/okd/tasks/openshift.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -# OpenShift Resources -- name: Create a project - community.kubernetes.k8s: - name: testing - kind: Project - api_version: v1 - apply: no - register: output - -- name: show output - debug: - var: output - -- name: Create deployment config - community.kubernetes.k8s: - state: present - inline: &dc - apiVersion: v1 - kind: DeploymentConfig - metadata: - name: elastic - labels: - app: galaxy - service: elastic - namespace: testing - spec: - template: - metadata: - labels: - app: galaxy - service: elastic - spec: - containers: - - name: elastic - volumeMounts: - - mountPath: /usr/share/elasticsearch/data - name: elastic-volume - command: ['elasticsearch'] - image: 'ansible/galaxy-elasticsearch:2.4.6' - volumes: - - name: elastic-volume - persistentVolumeClaim: - claimName: elastic-volume - replicas: 1 - strategy: - type: Rolling - register: output - -- name: Show output - debug: - var: output - -- name: Create deployment config again - community.kubernetes.k8s: - state: present - inline: *dc - register: output - -- name: DC creation should be idempotent - assert: - that: not output.changed diff --git a/tests/integration/targets/okd/tasks/setup_remote_tmp_dir.yml b/tests/integration/targets/okd/tasks/setup_remote_tmp_dir.yml deleted file mode 100644 index 8acdb49..0000000 --- a/tests/integration/targets/okd/tasks/setup_remote_tmp_dir.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -- name: create temporary directory - test_tempfile: - state: directory - suffix: .test - register: remote_tmp_dir - notify: - - delete temporary directory - -- name: record temporary directory - set_fact: - remote_tmp_dir: "{{ remote_tmp_dir.path }}" diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt new file mode 100644 index 0000000..4880ee6 --- /dev/null +++ b/tests/sanity/ignore-2.10.txt @@ -0,0 +1,2 @@ +plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc +plugins/modules/k8s.py validate-modules:return-syntax-error diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt new file mode 100644 index 0000000..4880ee6 --- /dev/null +++ b/tests/sanity/ignore-2.11.txt @@ -0,0 +1,2 @@ +plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc +plugins/modules/k8s.py validate-modules:return-syntax-error diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt new file mode 100644 index 0000000..bbfafd9 --- /dev/null +++ b/tests/sanity/ignore-2.9.txt @@ -0,0 +1 @@ +plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc