From 68f5cc8e33ce5e90caa8f511b6029cc1f4be2ebf Mon Sep 17 00:00:00 2001 From: Felix Matouschek Date: Mon, 17 Jul 2023 14:20:39 +0200 Subject: [PATCH] Update Ansible GitHub workflows The workflows committed in the first commit were missing the installation of required dependencies and other fixes. Signed-off-by: Felix Matouschek --- .github/workflows/ci.yml | 266 +++++++++++++++--- .github/workflows/docs.yml | 5 +- test-requirements.txt | 9 + tests/.kubeconfig | 18 ++ tests/integration/inventory | 2 + tests/sanity/ignore-2.10.txt | 0 tests/sanity/ignore-2.11.txt | 0 tests/sanity/ignore-2.12.txt | 0 tests/sanity/ignore-2.13.txt | 0 tests/sanity/ignore-2.14.txt | 0 tests/sanity/ignore-2.15.txt | 0 tests/sanity/ignore-2.9.txt | 0 tests/unit/conftest.py | 44 +++ tests/unit/modules/test_module_kubevirt_vm.py | 147 ++++++++++ tests/unit/requirements.txt | 3 + tests/unit/utils/ansible_module_mock.py | 51 ++++ 16 files changed, 505 insertions(+), 40 deletions(-) create mode 100644 test-requirements.txt create mode 100644 tests/.kubeconfig create mode 100644 tests/integration/inventory 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.12.txt create mode 100644 tests/sanity/ignore-2.13.txt create mode 100644 tests/sanity/ignore-2.14.txt create mode 100644 tests/sanity/ignore-2.15.txt create mode 100644 tests/sanity/ignore-2.9.txt create mode 100644 tests/unit/conftest.py create mode 100644 tests/unit/modules/test_module_kubevirt_vm.py create mode 100644 tests/unit/requirements.txt create mode 100644 tests/unit/utils/ansible_module_mock.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ecc6bb..6563074 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,18 @@ on: schedule: - cron: '0 6 * * *' jobs: + prereq: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v2 + with: + path: ansible_collections/kubernetes/kubevirt + fetch-depth: 0 + - run: | + mkdir -p /home/runner/.kube/ + cp -rp ${GITHUB_WORKSPACE}/ansible_collections/kubernetes/kubevirt/tests/.kubeconfig /home/runner/.kube/config + cat /home/runner/.kube/config sanity: uses: ansible-network/github_actions/.github/workflows/sanity.yml@main with: @@ -71,50 +83,232 @@ jobs: { "ansible-version": "devel", "python-version": "3.8" + }, + { + "ansible-version": "devel", + "python-version": "3.9" } ] - integration: + unit-source: + uses: ansible-network/github_actions/.github/workflows/unit_source.yml@main + needs: + - prereq + with: + matrix_exclude: >- + [ + { + "python-version": "3.11" + }, + { + "ansible-version": "stable-2.12", + "python-version": "3.7" + }, + { + "ansible-version": "stable-2.13", + "python-version": "3.7" + }, + { + "ansible-version": "stable-2.12", + "python-version": "3.8" + }, + { + "ansible-version": "stable-2.13", + "python-version": "3.8" + }, + { + "ansible-version": "stable-2.14", + "python-version": "3.7" + }, + { + "ansible-version": "stable-2.14", + "python-version": "3.8" + }, + { + "ansible-version": "stable-2.15", + "python-version": "3.7" + }, + { + "ansible-version": "stable-2.15", + "python-version": "3.8" + }, + { + "ansible-version": "milestone", + "python-version": "3.7" + }, + { + "ansible-version": "milestone", + "python-version": "3.8" + }, + { + "ansible-version": "devel", + "python-version": "3.7" + }, + { + "ansible-version": "devel", + "python-version": "3.8" + }, + { + "ansible-version": "devel", + "python-version": "3.9" + } + ] + collection_pre_install: '' + splitter: + env: + source_dir: "./source" runs-on: ubuntu-latest - name: I (${{ matrix.ansible }}+py${{ matrix.python }}) + outputs: + test_targets: ${{ steps.display.outputs.test_targets }} + steps: + - name: Checkout the collection repository + uses: actions/checkout@v3 + with: + path: ${{ env.source_dir }} + fetch-depth: "0" + + - name: list changes for pull request + id: splitter + uses: ansible-network/github_actions/.github/actions/ansible_test_splitter@main + with: + collections_to_test: ${{ env.source_dir }} + total_jobs: 8 + + - name: display targets + id: display + run: echo "test_targets=${{ steps.splitter.outputs.test_targets }}" >> $GITHUB_OUTPUT + shell: bash + + integration: + needs: + - splitter + env: + source: "./source" + cloud_common: "./cloudcommon" + ansible_posix: "./ansible_posix" + test_targets: ${{ needs.splitter.outputs.test_targets }} + runs-on: ubuntu-latest + timeout-minutes: 60 strategy: fail-fast: false matrix: - ansible: - - stable-2.9 - - stable-2.11 + ansible-version: - stable-2.12 - - stable-2.13 + - milestone - devel - python: - - '3.8' - - '3.9' - - '3.10' + python-version: + - "3.8" + - "3.9" exclude: - # Because ansible-test doesn't support Python 3.9 for Ansible 2.9 - # and Python 3.10 is supported in 2.12 or later. - - ansible: stable-2.9 - python: '3.9' - - ansible: stable-2.9 - python: '3.10' - - ansible: stable-2.10 - python: '3.10' - - ansible: stable-2.11 - python: '3.10' - + - ansible-version: stable-2.9 + python-version: 3.9 + - ansible-version: stable-2.9 + python-version: 3.10 + - ansible-version: stable-2.9 + python-version: 3.11 + - ansible-version: stable-2.12 + python-version: 3.11 + - ansible-version: stable-2.13 + python-version: 3.11 + - ansible-version: stable-2.14 + python-version: 3.8 + - ansible-version: stable-2.15 + python-version: 3.8 + - ansible-version: milestone + python-version: 3.8 + - ansible-version: devel + python-version: 3.8 + enable-turbo-mode: + - true + - false + job-index: [1, 2, 3, 4, 5, 6, 7, 8] + name: "integration-py${{ matrix.python-version }}-${{ matrix.ansible-version }}-turbo-mode=${{ matrix.enable-turbo-mode }}-${{ matrix.job-index }}" steps: - - name: >- - Perform integration testing against - Ansible version ${{ matrix.ansible }} - under Python ${{ matrix.python }} - uses: ansible-community/ansible-test-gh-action@release/v1 + - name: Read ansible-test targets + id: read-targets + run: >- + echo "ansible_test_targets=$(echo "${{ env.test_targets }}" | sed s/';'/'\n'/g | + grep "kubernetes.core-${{ matrix.job-index }}" | cut -d ':' -f2 | sed s/','/' '/g)" >> $GITHUB_OUTPUT + shell: bash + + - name: Display targets + run: >- + echo "targets to test: $ANSIBLE_TARGETS" + shell: bash + env: + ANSIBLE_TARGETS: ${{ steps.read-targets.outputs.ansible_test_targets }} + + - name: Checkout kubernetes.core repository + uses: actions/checkout@v3 with: - ansible-core-version: ${{ matrix.ansible }} - # OPTIONAL command to run before invoking `ansible-test integration` - # pre-test-cmd: - target-python-version: ${{ matrix.python }} - testing-type: integration - # OPTIONAL If your integration tests require code - # from other collections, install them like this - test-deps: >- - ansible.netcommon - kubernetes.core + path: ${{ env.source }} + fetch-depth: "0" + if: steps.read-targets.outputs.ansible_test_targets != '' + + - name: checkout ansible-collections/cloud.common + uses: ansible-network/github_actions/.github/actions/checkout_dependency@main + with: + repository: ansible-collections/cloud.common + path: ${{ env.cloud_common }} + ref: main + if: steps.read-targets.outputs.ansible_test_targets != '' + + - name: checkout ansible-collections/ansible.posix + uses: ansible-network/github_actions/.github/actions/checkout_dependency@main + with: + repository: ansible-collections/ansible.posix + path: ${{ env.ansible_posix }} + ref: main + if: steps.read-targets.outputs.ansible_test_targets != '' + + - name: install kubernetes.core collection + id: install-collection + uses: ansible-network/github_actions/.github/actions/build_install_collection@main + with: + install_python_dependencies: true + source_path: ${{ env.source }} + if: steps.read-targets.outputs.ansible_test_targets != '' + + - name: install cloud.common collection + uses: ansible-network/github_actions/.github/actions/build_install_collection@main + with: + install_python_dependencies: true + source_path: ${{ env.cloud_common }} + if: steps.read-targets.outputs.ansible_test_targets != '' + + - name: install ansible.posix collection + uses: ansible-network/github_actions/.github/actions/build_install_collection@main + with: + install_python_dependencies: true + source_path: ${{ env.ansible_posix }} + if: steps.read-targets.outputs.ansible_test_targets != '' + + - name: create kubernetes cluster + uses: helm/kind-action@v1.4.0 + if: steps.read-targets.outputs.ansible_test_targets != '' + + - name: Run integration tests + uses: ansible-network/github_actions/.github/actions/ansible_test_integration@main + with: + collection_path: ${{ steps.install-collection.outputs.collection_path }} + python_version: ${{ matrix.python-version }} + ansible_version: ${{ matrix.ansible-version }} + ansible_test_targets: ${{ steps.read-targets.outputs.ansible_test_targets }} + ansible_test_environment: | + ENABLE_TURBO_MODE=${{ matrix.enable-turbo-mode }} + if: steps.read-targets.outputs.ansible_test_targets != '' + all_green: + if: ${{ always() }} + needs: + - sanity + - unit-source + - integration + runs-on: ubuntu-latest + steps: + - run: >- + python -c "assert set([ + '${{ needs.unit-source.result }}', + '${{ needs.integration.result }}' + ]) == {'success'}" + - run: >- + python -c "assert '${{ needs.sanity.result }}' + in ['success', 'failure']" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1cfa060..be474e8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -48,10 +48,7 @@ jobs: - name: Create default collection path run: | mkdir -p /home/runner/.ansible/ - cp -rp /home/runner/work/kubevirt/kubevirt/ansible_collections /home/runner/.ansible/collections/ - ls -l /home/runner/.ansible/collections/ansible_collections/ - ls -l /home/runner/.ansible/collections/ansible_collections/kubernetes/ - ls -l /home/runner/.ansible/collections/ansible_collections/kubernetes/kubevirt/ + cp -rp ${GITHUB_WORKSPACE}/ansible_collections /home/runner/.ansible/collections/ - name: Create changelog and documentation uses: ansible-middleware/collection-docs-action@main diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..47283a2 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,9 @@ +kubernetes-validate +coverage==4.5.4 +mock +pytest +pytest-xdist +pytest-mock +pytest-forked +virtualenv +pytest-ansible diff --git a/tests/.kubeconfig b/tests/.kubeconfig new file mode 100644 index 0000000..7cfa77b --- /dev/null +++ b/tests/.kubeconfig @@ -0,0 +1,18 @@ +apiVersion: v1 +clusters: +- cluster: + insecure-skip-tls-verify: true + server: http://localhost:12345 + name: development +contexts: +- context: + cluster: development + user: developer + name: dev-frontend +current-context: dev-frontend +kind: Config +preferences: {} +users: +- name: developer + user: + token: ZDNffLzSlp8aSS0fht_tRnPMTOjxqgJGCyi_iy0ecUw diff --git a/tests/integration/inventory b/tests/integration/inventory new file mode 100644 index 0000000..25b2ccd --- /dev/null +++ b/tests/integration/inventory @@ -0,0 +1,2 @@ +[testgroup] +testhost ansible_connection="local" ansible_pipelining="yes" ansible_python_interpreter="/home/guido/Development/virtualenv/2.15/bin/python" diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 0000000..20615ad --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json +import sys +from io import BytesIO + +import pytest + +import ansible.module_utils.basic +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping + + +@pytest.fixture +def stdin(mocker, request): + old_args = ansible.module_utils.basic._ANSIBLE_ARGS + ansible.module_utils.basic._ANSIBLE_ARGS = None + old_argv = sys.argv + sys.argv = ["ansible_unittest"] + + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if "ANSIBLE_MODULE_ARGS" not in request.param: + request.param = {"ANSIBLE_MODULE_ARGS": request.param} + if "_ansible_remote_tmp" not in request.param["ANSIBLE_MODULE_ARGS"]: + request.param["ANSIBLE_MODULE_ARGS"]["_ansible_remote_tmp"] = "/tmp" + if "_ansible_keep_remote_files" not in request.param["ANSIBLE_MODULE_ARGS"]: + request.param["ANSIBLE_MODULE_ARGS"]["_ansible_keep_remote_files"] = False + args = json.dumps(request.param) + else: + raise Exception("Malformed data to the stdin pytest fixture") + + fake_stdin = BytesIO(to_bytes(args, errors="surrogate_or_strict")) + mocker.patch("ansible.module_utils.basic.sys.stdin", mocker.MagicMock()) + mocker.patch("ansible.module_utils.basic.sys.stdin.buffer", fake_stdin) + + yield fake_stdin + + ansible.module_utils.basic._ANSIBLE_ARGS = old_args + sys.argv = old_argv diff --git a/tests/unit/modules/test_module_kubevirt_vm.py b/tests/unit/modules/test_module_kubevirt_vm.py new file mode 100644 index 0000000..a5931df --- /dev/null +++ b/tests/unit/modules/test_module_kubevirt_vm.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, 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 + +import unittest + +from unittest.mock import patch, ANY + +from ansible.module_utils import basic +from ansible_collections.kubernetes.core.plugins.module_utils.k8s import runner +from ansible_collections.kubernetes.kubevirt.plugins.modules import kubevirt_vm +from ansible_collections.kubernetes.kubevirt.tests.unit.utils.ansible_module_mock import ( + AnsibleFailJson, + AnsibleExitJson, + exit_json, + fail_json, + set_module_args, + get_api_client +) + +FIXTURE1 = { + "apiVersion": "kubevirt.io/v1", + "kind": "VirtualMachine", + "metadata": { + "name": "testvm", + "namespace": "default", + "labels": { + "environment": "staging", + "service": "loadbalancer" + } + }, + "spec": { + "running": True, + "template": { + "metadata": { + "labels": { + "environment": "staging", + "service": "loadbalancer" + } + }, + "spec": { + "domain": { + "devices": {} + }, + "terminationGracePeriodSeconds": 180 + } + } + } +} + +METADATA = '''apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: "testvm" + namespace: "default" + labels: + environment: staging + service: loadbalancer +spec: + running: True + template: + metadata: + labels: + environment: staging + service: loadbalancer + spec: + domain: + devices: {} + terminationGracePeriodSeconds: 180''' + +FIXTURE2 = { + 'name': 'testvm', + 'namespace': 'default', + 'state': 'present', + 'labels': { + 'service': 'loadbalancer', + 'environment': 'staging' + }, + 'api_version': 'kubevirt.io/v1', 'running': True, 'termination_grace_period': 180, 'wait': False, 'wait_sleep': 5, 'wait_timeout': 120, 'force': False, + 'generate_name': None, 'annotations': None, 'instancetype': None, 'preference': None, 'infer_from_volume': None, 'clear_revision_name': None, + 'interfaces': None, 'networks': None, 'volumes': None, 'kubeconfig': None, 'context': None, 'host': None, 'api_key': None, 'username': None, + 'password': None, 'validate_certs': None, 'ca_cert': None, 'client_cert': None, 'client_key': None, 'proxy': None, 'no_proxy': None, 'proxy_headers': None, + 'persist_config': None, 'impersonate_user': None, 'impersonate_groups': None, 'delete_options': None, + 'resource_definition': METADATA, + 'wait_condition': { + 'type': 'Ready', + 'status': True + } +} + + +class TestCreateVM(unittest.TestCase): + def setUp(self): + self.mock_module_helper = patch.multiple( + basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json + ) + self.mock_module_helper.start() + + self.mock_runner = patch.multiple( + runner, + get_api_client=get_api_client + ) + self.mock_runner.start() + + # Stop the patch after test execution + # like tearDown but executed also when the setup failed + self.addCleanup(self.mock_module_helper.stop) + self.addCleanup(self.mock_runner.stop) + + def test_module_fail_when_required_args_missing(self): + with self.assertRaises(AnsibleFailJson): + set_module_args({}) + kubevirt_vm.main() + + def test_create(self): + set_module_args( + { + "name": "testvm", + "namespace": "default", + "state": "present", + "labels": { + "service": "loadbalancer", + "environment": "staging" + } + } + ) + with patch.object(runner, "perform_action") as mock_run_command: + mock_run_command.return_value = ( + { + "method": "create", + "changed": True, + "result": "success" + } + ) # successful execution + with self.assertRaises(AnsibleExitJson) as result: + kubevirt_vm.main() + mock_run_command.assert_called_once_with( + ANY, + FIXTURE1, + FIXTURE2, + ) diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt new file mode 100644 index 0000000..55c7255 --- /dev/null +++ b/tests/unit/requirements.txt @@ -0,0 +1,3 @@ +pytest +PyYAML +kubernetes diff --git a/tests/unit/utils/ansible_module_mock.py b/tests/unit/utils/ansible_module_mock.py new file mode 100644 index 0000000..8af5232 --- /dev/null +++ b/tests/unit/utils/ansible_module_mock.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# This module maock the AnsibleModule class for more information please visite +# https://docs.ansible.com/ansible/latest/dev_guide/testing_units_modules.html#module-argument-processing + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json + +from ansible.module_utils import basic +from ansible.module_utils.common.text.converters import to_bytes + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({"ANSIBLE_MODULE_ARGS": args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + pass + + +def exit_json(*args, **kwargs): + """function to patch over exit_json; package return data into an exception""" + if "changed" not in kwargs: + kwargs["changed"] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + """function to patch over fail_json; package return data into an exception""" + kwargs["failed"] = True + raise AnsibleFailJson(kwargs) + + +def get_api_client(*args, **kwargs): + """function to patch over get_api_client """ + pass