diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0276807..dd6cf70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -172,5 +172,6 @@ jobs: [ "kubevirt_vm", "kubevirt_vm_info", + "kubevirt_vmi_info", "inventory_kubevirt" ] diff --git a/.gitignore b/.gitignore index b52a1a9..0122fc0 100644 --- a/.gitignore +++ b/.gitignore @@ -163,4 +163,5 @@ kubevirt.core /tests/integration/targets/kubevirt_vm/wait_for_vm.yml /tests/integration/targets/kubevirt_vm/files /tests/integration/targets/kubevirt_vm_info/playbook.yml +/tests/integration/targets/kubevirt_vmi_info/playbook.yml kubevirt-cache diff --git a/plugins/modules/kubevirt_vmi_info.py b/plugins/modules/kubevirt_vmi_info.py new file mode 100644 index 0000000..3b3d3ca --- /dev/null +++ b/plugins/modules/kubevirt_vmi_info.py @@ -0,0 +1,187 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2023 Red Hat, Inc. +# Based on the kubernetes.core.k8s_info module +# Apache License 2.0 (see LICENSE or http://www.apache.org/licenses/LICENSE-2.0) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +module: kubevirt_vmi_info + +short_description: Describe KubeVirt VirtualMachineInstances + +author: +- "KubeVirt.io Project (!UNKNOWN)" + +description: + - Use the Kubernetes Python client to perform read operations on KubeVirt C(VirtualMachineInstances). + - Pass options to find C(VirtualMachineInstances) as module arguments. + - Authenticate using either a config file, certificates, password or token. + - Supports check mode. + +extends_documentation_fragment: + - kubevirt.core.kubevirt_auth_options + +options: + api_version: + description: + - Use this to set the API version of KubeVirt. + type: str + default: kubevirt.io/v1 + name: + description: + - Specify the name of the C(VirtualMachineInstance). + type: str + namespace: + description: + - Specify the namespace of C(VirtualMachineInstances). + type: str + label_selectors: + description: List of label selectors to use to filter results. + type: list + elements: str + default: [] + field_selectors: + description: List of field selectors to use to filter results. + type: list + elements: str + default: [] + wait: + description: + - Whether to wait for the C(VirtualMachineInstance) to end up in the ready state. + - By default this is waiting for the C(VirtualMachineInstance) to be up and running. + type: bool + wait_sleep: + description: + - Number of seconds to sleep between checks. + - Ignored if O(wait) is not set. + default: 5 + type: int + wait_timeout: + description: + - How long in seconds to wait for the resource to end up in the ready state. + - Ignored if O(wait) is not set. + default: 120 + type: int + +requirements: + - "python >= 3.9" + - "kubernetes >= 28.1.0" + - "PyYAML >= 3.11" +""" + +EXAMPLES = """ +- name: Get an existing VirtualMachineInstance + kubevirt.core.kubevirt_vmi_info: + name: testvmi + namespace: default + register: default_testvmi + +- name: Get a list of all VirtualMachinesInstances + kubevirt.core.kubevirt_vmi_info: + namespace: default + register: vmi_list + +- name: Get a list of all VirtualMachineInstances from any namespace + kubevirt.core.kubevirt_vmi_info: + register: vmi_list + +- name: Search for all VirtualMachineInstances labelled app=test + kubevirt.core.kubevirt_vmi_info: + label_selectors: + - app=test + +- name: Wait until the VirtualMachineInstace is ready + kubevirt.core.kubevirt_vm_info: + name: testvm + namespace: default + wait: true +""" + +RETURN = """ +api_found: + description: + - Whether the specified O(api_version) and C(VirtualMachineInstance) C(Kind) were successfully mapped to an existing API on the target cluster. + returned: always + type: bool +resources: + description: + - The C(VirtualMachineInstances) that exist. + 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 C(REST) resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: dict + spec: + description: Specific attributes of the C(VirtualMachineInstance). Can vary based on the O(api_version). + returned: success + type: dict + status: + description: Current status details for the C(VirtualMachineInstance). + returned: success + type: dict +""" + +from copy import deepcopy + +from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( + AnsibleModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( + AUTH_ARG_SPEC, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) + +from ansible_collections.kubevirt.core.plugins.module_utils.info import ( + INFO_ARG_SPEC, + execute_info_module, +) + + +def arg_spec(): + """ + arg_spec defines the argument spec of this module. + """ + spec = {} + spec.update(deepcopy(INFO_ARG_SPEC)) + spec.update(deepcopy(AUTH_ARG_SPEC)) + + return spec + + +def main(): + """ + main instantiates the AnsibleK8SModule and runs the module. + """ + module = AnsibleK8SModule( + module_class=AnsibleModule, + argument_spec=arg_spec(), + supports_check_mode=True, + ) + + # Set kind to query for VirtualMachineInstances + kind = "VirtualMachineInstance" + + # Set wait_condition to allow waiting for the ready state of the VirtualMachineInstance + wait_condition = {"type": "Ready", "status": True} + + execute_info_module(module, kind, wait_condition) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/kubevirt_vmi_info/generate.yml b/tests/integration/targets/kubevirt_vmi_info/generate.yml new file mode 100644 index 0000000..aaad406 --- /dev/null +++ b/tests/integration/targets/kubevirt_vmi_info/generate.yml @@ -0,0 +1,13 @@ +--- +- name: Generate test files + connection: local + gather_facts: false + hosts: localhost + tasks: + - name: Generate test files + ansible.builtin.template: + src: "{{ item }}.yml.j2" + dest: "{{ item }}.yml" + mode: "0644" + with_items: + - playbook diff --git a/tests/integration/targets/kubevirt_vmi_info/playbook.yml.j2 b/tests/integration/targets/kubevirt_vmi_info/playbook.yml.j2 new file mode 100644 index 0000000..014b431 --- /dev/null +++ b/tests/integration/targets/kubevirt_vmi_info/playbook.yml.j2 @@ -0,0 +1,67 @@ +--- +- name: Create VM + connection: local + gather_facts: false + hosts: localhost + tasks: + - name: Create a VM + kubevirt.core.kubevirt_vm: + name: testvm + namespace: {{ NAMESPACE }} + instancetype: + name: u1.small + preference: + name: centos.stream9 + spec: + domain: + devices: {} + volumes: + - containerDisk: + image: quay.io/containerdisks/centos-stream:9 + name: containerdisk + wait: true + wait_timeout: 600 + +- name: Describe created VMI + connection: local + gather_facts: false + hosts: localhost + tasks: + - name: Describe a VMI + kubevirt.core.kubevirt_vmi_info: + name: testvm + namespace: {{ NAMESPACE }} + register: describe + - name: Assert module reported no changes + ansible.builtin.assert: + that: + - not describe.changed + - describe.resources | length == 1 + +- name: Delete VM + connection: local + gather_facts: false + hosts: localhost + tasks: + - name: Delete a VM + kubevirt.core.kubevirt_vm: + name: testvm + namespace: {{ NAMESPACE }} + state: absent + wait: true + +- name: Verify VM deletion + connection: local + gather_facts: false + hosts: localhost + tasks: + - name: Delete a VM + kubevirt.core.kubevirt_vm: + name: testvm + namespace: {{ NAMESPACE }} + state: absent + register: delete + - name: Assert module reported no changes + ansible.builtin.assert: + that: + - not delete.changed diff --git a/tests/integration/targets/kubevirt_vmi_info/runme.sh b/tests/integration/targets/kubevirt_vmi_info/runme.sh new file mode 100755 index 0000000..d090714 --- /dev/null +++ b/tests/integration/targets/kubevirt_vmi_info/runme.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -eux + +export ANSIBLE_CALLBACKS_ENABLED=ansible.posix.profile_tasks + +NAMESPACE="test-kubevirt-vmi-info-$(tr -dc '[:lower:]' < /dev/urandom | head -c 5)" + +cleanup() { + ansible localhost -m kubernetes.core.k8s -a "name=${NAMESPACE} api_version=v1 kind=Namespace state=absent" + rm -rf playbook.yml +} +trap cleanup EXIT + +# Prepare the test environment +ansible localhost -m kubernetes.core.k8s -a "name=${NAMESPACE} api_version=v1 kind=Namespace state=present" +ansible-playbook -e "NAMESPACE=${NAMESPACE}" generate.yml + +# Run the tests +ansible-playbook playbook.yml "$@" diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index 2409ebe..556d778 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -1,3 +1,4 @@ plugins/inventory/kubevirt.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm_info.py validate-modules:missing-gplv3-license +plugins/modules/kubevirt_vmi_info.py validate-modules:missing-gplv3-license diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index 2409ebe..556d778 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -1,3 +1,4 @@ plugins/inventory/kubevirt.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm_info.py validate-modules:missing-gplv3-license +plugins/modules/kubevirt_vmi_info.py validate-modules:missing-gplv3-license diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index 2409ebe..556d778 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -1,3 +1,4 @@ plugins/inventory/kubevirt.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm_info.py validate-modules:missing-gplv3-license +plugins/modules/kubevirt_vmi_info.py validate-modules:missing-gplv3-license diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index 2409ebe..556d778 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -1,3 +1,4 @@ plugins/inventory/kubevirt.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm_info.py validate-modules:missing-gplv3-license +plugins/modules/kubevirt_vmi_info.py validate-modules:missing-gplv3-license diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt index 2409ebe..556d778 100644 --- a/tests/sanity/ignore-2.18.txt +++ b/tests/sanity/ignore-2.18.txt @@ -1,3 +1,4 @@ plugins/inventory/kubevirt.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm.py validate-modules:missing-gplv3-license plugins/modules/kubevirt_vm_info.py validate-modules:missing-gplv3-license +plugins/modules/kubevirt_vmi_info.py validate-modules:missing-gplv3-license diff --git a/tests/unit/plugins/modules/test_kubevirt_vmi_info.py b/tests/unit/plugins/modules/test_kubevirt_vmi_info.py new file mode 100644 index 0000000..c004805 --- /dev/null +++ b/tests/unit/plugins/modules/test_kubevirt_vmi_info.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat, Inc. +# Apache License 2.0 (see LICENSE or http://www.apache.org/licenses/LICENSE-2.0) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import pytest + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + K8sService, +) +from ansible_collections.kubevirt.core.plugins.modules import ( + kubevirt_vmi_info, +) +from ansible_collections.kubevirt.core.plugins.module_utils import ( + info, +) +from ansible_collections.kubevirt.core.tests.unit.utils.ansible_module_mock import ( + AnsibleExitJson, + exit_json, + set_module_args, +) + +FIND_ARGS_DEFAULT = { + "kind": "VirtualMachineInstance", + "api_version": "kubevirt.io/v1", + "name": None, + "namespace": None, + "label_selectors": [], + "field_selectors": [], + "wait": None, + "wait_sleep": 5, + "wait_timeout": 120, + "condition": {"type": "Ready", "status": True}, +} + +FIND_ARGS_NAME_NAMESPACE = FIND_ARGS_DEFAULT | { + "name": "testvm", + "namespace": "default", +} + +FIND_ARGS_LABEL_SELECTOR = FIND_ARGS_DEFAULT | { + "label_selectors": ["app=test"], +} + +FIND_ARGS_FIELD_SELECTOR = FIND_ARGS_DEFAULT | { + "field_selectors": ["app=test"], +} + + +@pytest.mark.parametrize( + "module_args,find_args", + [ + ({}, FIND_ARGS_DEFAULT), + ({"name": "testvm", "namespace": "default"}, FIND_ARGS_NAME_NAMESPACE), + ({"label_selectors": "app=test"}, FIND_ARGS_LABEL_SELECTOR), + ({"field_selectors": "app=test"}, FIND_ARGS_FIELD_SELECTOR), + ], +) +def test_module(mocker, module_args, find_args): + mocker.patch.object(AnsibleModule, "exit_json", exit_json) + mocker.patch.object(info, "get_api_client") + + find = mocker.patch.object( + K8sService, + "find", + return_value={ + "api_found": True, + "failed": False, + "resources": [], + }, + ) + + with pytest.raises(AnsibleExitJson): + set_module_args(module_args) + kubevirt_vmi_info.main() + + find.assert_called_once_with(**find_args)