Skip to content

Commit

Permalink
* [e2e] add more terraform resource functions
Browse files Browse the repository at this point in the history
  • Loading branch information
lanfon72 committed Nov 14, 2023
1 parent 4e07f2c commit a6e0057
Showing 1 changed file with 168 additions and 87 deletions.
255 changes: 168 additions & 87 deletions harvester_e2e_tests/fixtures/terraform.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import re
import json
import operator
from pathlib import Path
from datetime import datetime
from subprocess import run, PIPE
from dataclasses import dataclass

import pytest
from pkg_resources import parse_version

TF_PROVIDER = '''
terraform {{
Expand All @@ -29,6 +32,11 @@ def remove_ansicode(ctx):
return re.sub(r"\x1b|\[\d+m", "", ctx)


@pytest.fixture(scope="session")
def tf_script_dir(request):
return Path(request.config.getoption('--terraform-scripts-location'))


@pytest.fixture(scope="session")
def tf_provider_version(request):
version = request.config.getoption('--terraform-provider-harvester')
Expand All @@ -41,17 +49,16 @@ def tf_provider_version(request):


@pytest.fixture(scope="session")
def tf_executor(request):
path = Path(request.config.getoption('--terraform-scripts-location'))
run(str(path / "terraform_install.sh"), stdout=PIPE, stderr=PIPE)
executor = path / "bin/terraform"
def tf_executor(tf_script_dir):
run(str(tf_script_dir / "terraform_install.sh"), stdout=PIPE, stderr=PIPE)
executor = tf_script_dir / "bin/terraform"
assert executor.is_file()

yield executor


@pytest.fixture(scope="session")
def tf_harvester(request, api_client, tf_executor, tf_provider_version):
def tf_harvester(api_client, tf_script_dir, tf_provider_version, tf_executor):
class TerraformHarvester:
def __init__(self, executor, workdir):
self.executor = executor.resolve()
Expand Down Expand Up @@ -92,8 +99,7 @@ def apply_resource(self, resource_type, resource_name):
def destroy_resource(self, resource_type, resource_name):
return self.execute(f"destroy -auto-approve -target {resource_type}.{resource_name}")

path = Path(request.config.getoption('--terraform-scripts-location'))
harv = TerraformHarvester(tf_executor, path / datetime.now().strftime("%Hh%Mm_%m-%d"))
harv = TerraformHarvester(tf_executor, tf_script_dir / datetime.now().strftime("%Hh%Mm_%m-%d"))
kuebconfig = api_client.generate_kubeconfig()
out, err, exc_code = harv.initial_provider(kuebconfig, tf_provider_version)
assert not err and 0 == exc_code
Expand All @@ -102,84 +108,159 @@ def destroy_resource(self, resource_type, resource_name):


@pytest.fixture(scope="session")
def tf_resource(request, tf_provider_version):
from dataclasses import dataclass

@dataclass
class ResourceContext:
type: str
name: str
ctx: str

class TerraformResource:
@classmethod
def _support_version(cls, version):
return True

@classmethod
def from_version(cls, version):
for c in cls.__subclasses__():
if c._support_version(version):
return c()
return cls()

def __init__(self):
self.executor = Path("./terraform_test_artifacts/json2hcl").resolve()

def convert_to_hcl(self, json_spec, raw=False):
rv = run(f"echo {json.dumps(json_spec)!r} | {self.executor!s}",
shell=True, stdout=PIPE, stderr=PIPE)
if raw:
return rv
if rv.stderr:
raise TypeError(rv.stderr, rv.stdout, rv.returncode)
out = rv.stdout.decode()
out = re.sub(r'"resource"', "resource", out) # resource should not quote
out = re.sub(r"\"(.+?)\" =", r"\1 =", out) # property should not quote
return out

def ssh_key(self, resource_name, name, public_key, *, convert=True, **kwargs):
spec = {"name": name, "public_key": public_key}
spec.update(kwargs)
rv = dict(resource=dict(harvester_ssh_key={resource_name: spec}))

if convert:
return ResourceContext("harvester_ssh_key", resource_name, self.convert_to_hcl(rv))
def tf_resource(tf_provider_version):
converter = Path("./terraform_test_artifacts/json2hcl")
return BaseTerraformResource.from_version(tf_provider_version)(converter)


@dataclass
class ResourceContext:
type: str
name: str
ctx: str


class BaseTerraformResource:
#: :type: Tuple[str, callable[(str, str), bool]]
#: Be used to adjust whether the class is support to specific version,
#: this would be used in cls.is_support, worked as `version op target`
support_to = ("0.0.0", operator.eq)

@classmethod
def is_support(cls, target_version):
version, comparator = cls.support_to
return comparator(parse_version(version), parse_version(target_version))

@classmethod
def from_version(cls, version):
for c in cls.__subclasses__()[::-1]:
subcls = c.from_version(version)
if subcls.is_support(version):
return subcls
return cls

def __init__(self, converter):
self.executor = Path(converter).resolve()

def convert_to_hcl(self, json_spec, raw=False):
rv = run(f"echo {json.dumps(json_spec)!r} | {self.executor!s}",
shell=True, stdout=PIPE, stderr=PIPE)
if raw:
return rv

def storage_class(
self, resource_name, name, replicas=1, stale_timeout=30, migratable="true",
*, convert=True, **kwargs
):
spec = {
"name": name,
"parameters": {
"migratable": migratable,
"numberOfReplicas": str(replicas),
"staleReplicaTimeout": str(stale_timeout)
}
}
other_params = kwargs.pop('parameters', {})
spec.update(kwargs)
spec['parameters'].update(other_params)
rv = dict(resource=dict(harvester_storageclass={resource_name: spec}))

if convert:
return ResourceContext(
"harvester_storageclass", resource_name, self.convert_to_hcl(rv)
)
return rv

def volume(self, resource_name, name, size, *, convert=True, **kwargs):
spec = {
"name": name,
"size": size if isinstance(size, str) else f"{size}Gi"
}
spec.update(kwargs)
rv = dict(resource=dict(harvester_volume={resource_name: spec}))

if convert:
return ResourceContext("harvester_volume", resource_name, self.convert_to_hcl(rv))
return rv

return TerraformResource.from_version(tf_provider_version)
if rv.stderr:
raise TypeError(rv.stderr, rv.stdout, rv.returncode)
out = rv.stdout.decode()
out = re.sub(r'"resource"', "resource", out) # resource should not quote
out = re.sub(r"\"(.+?)\" =", r"\1 =", out) # property should not quote
return out

def make_resource(self, resource_type, resource_name, *, convert=True, **properties):
rv = dict(resource={resource_type: {resource_name: properties}})
if convert:
return ResourceContext(resource_type, resource_name, self.convert_to_hcl(rv))
return rv


class TerraformResource(BaseTerraformResource):
support_to = ("0.0.0", operator.le)

def ssh_key(self, resource_name, name, public_key, *, convert=True, **properties):
return self.make_resource(
"harvester_ssh_key", resource_name, name=name, public_key=public_key,
convert=convert, **properties
)

def volume(self, resource_name, name, size=1, *, convert=True, **properties):
size = size if isinstance(size, str) else f"{size}Gi"
return self.make_resource(
"harvester_volume", resource_name, name=name, size=size,
convert=convert, **properties
)

def image_download(
self, resource_name, name, display_name, url, *, convert=True, **properties
):
return self.make_resource(
"harvester_image", resource_name, name=name, display_name=display_name, url=url,
source_type="download", convert=convert, **properties
)

def image_export_from_volume(
self, resource_name, name, display_name, pvc_name, pvc_namespace,
*, convert=True, **properties
):
return self.make_resource(
"harvester_image", resource_name, name=name, display_name=display_name,
pvc_name=pvc_name, pvc_namespace=pvc_namespace, source_type="export-from-volume",
convert=convert, **properties
)

def virtual_machine(self, resource_name, name, disks, nics, *, convert=True, **properties):
disks.extend(properties.pop("disk", []))
nics.extend(properties.pop("network_interface", []))
return self.make_resource(
"harvester_virtualmachine", resource_name, name=name, disk=disks,
network_interface=nics, convert=convert, **properties
)

vm = virtual_machine # alias

def network(self, resource_name, name, vlan_id, *, convert=True, **properties):
return self.make_resource(
"harvester_network", resource_name, vlan_id=vlan_id, convert=convert, **properties
)


class TerraformResource_060(TerraformResource):
support_to = ("0.6.0", operator.le)

def storage_class(
self, resource_name, name, replicas=1, stale_timeout=30, migratable="true",
*, convert=True, **properties
):
params = {
"migratable": migratable,
"numberOfReplicas": str(replicas),
"staleReplicaTimeout": str(stale_timeout)
}
params.update(properties.pop('parameters', {}))
return self.make_resource(
"harvester_storageclass", resource_name, name=name, parameters=params,
convert=convert, **properties
)

def cluster_network(self, resource_name, name, *, convert=True, **properties):
return self.make_resource(
"harvester_clusternetwork", resource_name, convert=convert, **properties
)

def vlanconfig(
self, resource_name, name, cluster_network_name, nics, *, convert=True, **properties
):
uplink = properties.pop('uplink', dict())
uplink['nics'] = nics

return self.make_resource(
"harvester_vlanconfig", resource_name, name=name, uplink=uplink,
cluster_network_name=cluster_network_name, convert=convert, **properties
)

def network(
self, resource_name, name, vlan_id, cluster_network_name, *, convert=True, **properties
):
return super().network(
resource_name, name, vlan_id, cluster_network_name=cluster_network_name,
convert=convert, **properties
)


class TerraformResource_063(TerraformResource_060):
support_to = ("0.6.3", operator.le)

def cloudinit_secret(
self, resource_name, name, user_data="", network_data="", *, convert=True, **properties
):
return self.make_resource(
"harvester_cloudinit_secret", resource_name,
user_data=user_data, network_data=network_data, convert=convert, **properties
)

0 comments on commit a6e0057

Please sign in to comment.