cleanup(kubevirt_vm): Drop dependency on Jinja2

Drop the dependency on Jinja2 in the kubevirt_vm module by using dicts
to construct a VM instead of rendering jinja templates internally.

Signed-off-by: Felix Matouschek <fmatouschek@redhat.com>
This commit is contained in:
Felix Matouschek
2024-07-03 14:40:50 +02:00
parent 665981c23b
commit c096f069e3
5 changed files with 84 additions and 134 deletions

View File

@@ -55,7 +55,6 @@ ansible-galaxy collection install -r requirements.yml
#### Python libraries #### Python libraries
- jinja2
- jsonpatch - jsonpatch
- kubernetes>=28.1.0 - kubernetes>=28.1.0
- PyYAML>=3.11 - PyYAML>=3.11

View File

@@ -151,7 +151,6 @@ requirements:
- "kubernetes >= 28.1.0" - "kubernetes >= 28.1.0"
- "PyYAML >= 3.11" - "PyYAML >= 3.11"
- "jsonpatch" - "jsonpatch"
- "jinja2"
""" """
EXAMPLES = """ EXAMPLES = """
@@ -263,7 +262,6 @@ result:
from copy import deepcopy from copy import deepcopy
from typing import Dict from typing import Dict
import traceback
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
AnsibleModule, AnsibleModule,
@@ -282,91 +280,48 @@ from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions imp
CoreException, CoreException,
) )
try:
import yaml
except ImportError:
HAS_YAML = False
YAML_IMPORT_ERROR = traceback.format_exc()
else:
HAS_YAML = True
YAML_IMPORT_ERROR = None
try: def create_vm(params: Dict) -> Dict:
from jinja2 import Environment
except ImportError:
HAS_JINJA = False
JINJA_IMPORT_ERROR = traceback.format_exc()
else:
HAS_JINJA = True
JINJA_IMPORT_ERROR = None
VM_TEMPLATE = """
apiVersion: {{ api_version }}
kind: VirtualMachine
metadata:
{% if name %}
name: {{ name }}
{% endif %}
{% if generate_name %}
generateName: {{ generate_name }}
{% endif %}
namespace: {{ namespace }}
{% if annotations %}
annotations:
{{ annotations | to_yaml | indent(4) }}
{%- endif %}
{% if labels %}
labels:
{{ labels | to_yaml | indent(4) }}
{%- endif %}
spec:
running: {{ running | lower }}
{% if instancetype %}
instancetype:
{{ instancetype | to_yaml | indent(4) }}
{%- endif %}
{% if preference %}
preference:
{{ preference | to_yaml | indent(4) }}
{%- endif %}
{% if data_volume_templates %}
dataVolumeTemplates:
{{ data_volume_templates | to_yaml | indent(2) }}
{%- endif %}
template:
{% if annotations or labels %}
metadata:
{% if annotations %}
annotations:
{{ annotations | to_yaml | indent(8) }}
{%- endif %}
{% if labels %}
labels:
{{ labels | to_yaml | indent(8) }}
{%- endif %}
{% endif %}
spec:
{% if spec %}
{{ spec | to_yaml | indent (6) }}
{%- else %}
domain:
devices: {}
{% endif %}
"""
def render_template(params: Dict) -> str:
""" """
render_template uses Jinja2 to render the VM_TEMPLATE into a string. create_vm constructs a VM from the module parameters.
""" """
env = Environment(autoescape=False, trim_blocks=True, lstrip_blocks=True) vm = {
env.filters["to_yaml"] = lambda data, *_, **kw: yaml.dump( "apiVersion": params["api_version"],
data, allow_unicode=True, default_flow_style=False, **kw "kind": "VirtualMachine",
) "metadata": {
"namespace": params["namespace"],
},
"spec": {
"running": params["running"],
"template": {"spec": {"domain": {"devices": {}}}},
},
}
template = env.from_string(VM_TEMPLATE.strip()) if (name := params.get("name")) is not None:
return template.render(params) vm["metadata"]["name"] = name
if (generate_name := params.get("generate_name")) is not None:
vm["metadata"]["generateName"] = generate_name
template_metadata = {}
if (annotations := params.get("annotations")) is not None:
vm["metadata"]["annotations"] = annotations
template_metadata["annotations"] = annotations
if (labels := params.get("labels")) is not None:
vm["metadata"]["labels"] = labels
template_metadata["labels"] = labels
if template_metadata:
vm["spec"]["template"]["metadata"] = template_metadata
if (instancetype := params.get("instancetype")) is not None:
vm["spec"]["instancetype"] = instancetype
if (preference := params.get("preference")) is not None:
vm["spec"]["preference"] = preference
if (data_volume_templates := params.get("data_volume_templates")) is not None:
vm["spec"]["dataVolumeTemplates"] = data_volume_templates
if (spec := params.get("spec")) is not None:
vm["spec"]["template"]["spec"] = spec
return vm
def arg_spec() -> Dict: def arg_spec() -> Dict:
@@ -428,8 +383,8 @@ def main() -> None:
supports_check_mode=True, supports_check_mode=True,
) )
# Set resource_definition to our rendered template # Set resource_definition to our constructed VM
module.params["resource_definition"] = render_template(module.params) module.params["resource_definition"] = create_vm(module.params)
# Set wait_condition to allow waiting for the ready state of the VirtualMachine # Set wait_condition to allow waiting for the ready state of the VirtualMachine
if module.params["running"]: if module.params["running"]:

View File

@@ -1,4 +1,3 @@
jinja2
jsonpatch jsonpatch
kubernetes>=28.1.0 kubernetes>=28.1.0
PyYAML>=3.11 PyYAML>=3.11

View File

@@ -8,8 +8,6 @@ __metaclass__ = type
import pytest import pytest
from yaml import dump
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible_collections.kubernetes.core.plugins.module_utils.k8s import runner from ansible_collections.kubernetes.core.plugins.module_utils.k8s import runner
from ansible_collections.kubevirt.core.plugins.modules import kubevirt_vm from ansible_collections.kubevirt.core.plugins.modules import kubevirt_vm
@@ -204,7 +202,7 @@ def module_params_delete(module_params_default):
def k8s_module_params_create(module_params_create, vm_definition_create): def k8s_module_params_create(module_params_create, vm_definition_create):
return module_params_create | { return module_params_create | {
"generate_name": None, "generate_name": None,
"resource_definition": dump(vm_definition_create, sort_keys=False), "resource_definition": vm_definition_create,
"wait_condition": {"type": "Ready", "status": True}, "wait_condition": {"type": "Ready", "status": True},
} }
@@ -213,7 +211,7 @@ def k8s_module_params_create(module_params_create, vm_definition_create):
def k8s_module_params_running(module_params_running, vm_definition_running): def k8s_module_params_running(module_params_running, vm_definition_running):
return module_params_running | { return module_params_running | {
"generate_name": None, "generate_name": None,
"resource_definition": dump(vm_definition_running, sort_keys=False), "resource_definition": vm_definition_running,
"wait_condition": {"type": "Ready", "status": True}, "wait_condition": {"type": "Ready", "status": True},
} }
@@ -222,7 +220,7 @@ def k8s_module_params_running(module_params_running, vm_definition_running):
def k8s_module_params_stopped(module_params_stopped, vm_definition_stopped): def k8s_module_params_stopped(module_params_stopped, vm_definition_stopped):
return module_params_stopped | { return module_params_stopped | {
"generate_name": None, "generate_name": None,
"resource_definition": dump(vm_definition_stopped, sort_keys=False), "resource_definition": vm_definition_stopped,
"wait_condition": {"type": "Ready", "status": False, "reason": "VMINotExists"}, "wait_condition": {"type": "Ready", "status": False, "reason": "VMINotExists"},
} }
@@ -231,7 +229,7 @@ def k8s_module_params_stopped(module_params_stopped, vm_definition_stopped):
def k8s_module_params_delete(module_params_delete, vm_definition_running): def k8s_module_params_delete(module_params_delete, vm_definition_running):
return module_params_delete | { return module_params_delete | {
"generate_name": None, "generate_name": None,
"resource_definition": dump(vm_definition_running, sort_keys=False), "resource_definition": vm_definition_running,
"wait_condition": {"type": "Ready", "status": True}, "wait_condition": {"type": "Ready", "status": True},
} }
@@ -305,7 +303,7 @@ def test_module(
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def render_template_params(): def create_vm_params():
return { return {
"api_version": "kubevirt.io/v1", "api_version": "kubevirt.io/v1",
"running": True, "running": True,
@@ -314,36 +312,36 @@ def render_template_params():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def render_template_params_annotations(render_template_params): def create_vm_params_annotations(create_vm_params):
return render_template_params | { return create_vm_params | {
"annotations": {"test": "test"}, "annotations": {"test": "test"},
} }
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def render_template_params_labels(render_template_params): def create_vm_params_labels(create_vm_params):
return render_template_params | { return create_vm_params | {
"labels": {"test": "test"}, "labels": {"test": "test"},
} }
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def render_template_params_instancetype(render_template_params): def create_vm_params_instancetype(create_vm_params):
return render_template_params | { return create_vm_params | {
"instancetype": {"name": "u1.medium"}, "instancetype": {"name": "u1.medium"},
} }
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def render_template_params_preference(render_template_params): def create_vm_params_preference(create_vm_params):
return render_template_params | { return create_vm_params | {
"preference": {"name": "fedora"}, "preference": {"name": "fedora"},
} }
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def render_template_params_datavolumetemplate(render_template_params): def create_vm_params_datavolumetemplate(create_vm_params):
return render_template_params | { return create_vm_params | {
"data_volume_templates": [ "data_volume_templates": [
{ {
"metadata": {"name": "testdv"}, "metadata": {"name": "testdv"},
@@ -364,22 +362,22 @@ def render_template_params_datavolumetemplate(render_template_params):
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def render_template_params_name(render_template_params): def create_vm_params_name(create_vm_params):
return render_template_params | { return create_vm_params | {
"name": "testvm", "name": "testvm",
} }
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def render_template_params_generate_name(render_template_params): def create_vm_params_generate_name(create_vm_params):
return render_template_params | { return create_vm_params | {
"generate_name": "testvm-1234", "generate_name": "testvm-1234",
} }
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def render_template_params_specs(render_template_params): def create_vm_params_specs(create_vm_params):
return render_template_params | { return create_vm_params | {
"spec": { "spec": {
"domain": { "domain": {
"devices": { "devices": {
@@ -395,7 +393,7 @@ def render_template_params_specs(render_template_params):
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def vm_template(): def created_vm():
return { return {
"apiVersion": "kubevirt.io/v1", "apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine", "kind": "VirtualMachine",
@@ -416,7 +414,7 @@ def vm_template():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def vm_template_labels(): def created_vm_labels():
return { return {
"apiVersion": "kubevirt.io/v1", "apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine", "kind": "VirtualMachine",
@@ -443,7 +441,7 @@ def vm_template_labels():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def vm_template_annotations(): def created_vm_annotations():
return { return {
"apiVersion": "kubevirt.io/v1", "apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine", "kind": "VirtualMachine",
@@ -470,7 +468,7 @@ def vm_template_annotations():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def vm_template_instancetype(): def created_vm_instancetype():
return { return {
"apiVersion": "kubevirt.io/v1", "apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine", "kind": "VirtualMachine",
@@ -492,7 +490,7 @@ def vm_template_instancetype():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def vm_template_preference(): def created_vm_preference():
return { return {
"apiVersion": "kubevirt.io/v1", "apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine", "kind": "VirtualMachine",
@@ -514,7 +512,7 @@ def vm_template_preference():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def vm_template_datavolumetemplate(): def created_vm_datavolumetemplate():
return { return {
"apiVersion": "kubevirt.io/v1", "apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine", "kind": "VirtualMachine",
@@ -551,7 +549,7 @@ def vm_template_datavolumetemplate():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def vm_template_name(): def created_vm_name():
return { return {
"apiVersion": "kubevirt.io/v1", "apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine", "kind": "VirtualMachine",
@@ -573,7 +571,7 @@ def vm_template_name():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def vm_template_generate_name(): def created_vm_generate_name():
return { return {
"apiVersion": "kubevirt.io/v1", "apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine", "kind": "VirtualMachine",
@@ -595,7 +593,7 @@ def vm_template_generate_name():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def vm_template_specs(): def created_vm_specs():
return { return {
"apiVersion": "kubevirt.io/v1", "apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine", "kind": "VirtualMachine",
@@ -622,20 +620,20 @@ def vm_template_specs():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"params,rendered_template", "params,expected",
[ [
("render_template_params", "vm_template"), ("create_vm_params", "created_vm"),
("render_template_params_annotations", "vm_template_annotations"), ("create_vm_params_annotations", "created_vm_annotations"),
("render_template_params_labels", "vm_template_labels"), ("create_vm_params_labels", "created_vm_labels"),
("render_template_params_instancetype", "vm_template_instancetype"), ("create_vm_params_instancetype", "created_vm_instancetype"),
("render_template_params_preference", "vm_template_preference"), ("create_vm_params_preference", "created_vm_preference"),
("render_template_params_datavolumetemplate", "vm_template_datavolumetemplate"), ("create_vm_params_datavolumetemplate", "created_vm_datavolumetemplate"),
("render_template_params_name", "vm_template_name"), ("create_vm_params_name", "created_vm_name"),
("render_template_params_generate_name", "vm_template_generate_name"), ("create_vm_params_generate_name", "created_vm_generate_name"),
("render_template_params_specs", "vm_template_specs"), ("create_vm_params_specs", "created_vm_specs"),
], ],
) )
def test_render_template(request, params, rendered_template): def test_create_vm(request, params, expected):
assert kubevirt_vm.render_template(request.getfixturevalue(params)) == dump( assert kubevirt_vm.create_vm(
request.getfixturevalue(rendered_template), sort_keys=False request.getfixturevalue(params)
) ) == request.getfixturevalue(expected)

View File

@@ -1,5 +1,4 @@
# This file is required by ansible-test units # This file is required by ansible-test units
jinja2
jsonpatch jsonpatch
kubernetes>=28.1.0 kubernetes>=28.1.0
PyYAML>=3.11 PyYAML>=3.11