Skip to content

Commit

Permalink
Merge pull request #392 from projectsyn/feat/commodore-inventory-comp…
Browse files Browse the repository at this point in the history
…onents-tenant-repo

Add support for tenant repo in `commodore inventory components`
  • Loading branch information
simu authored Dec 9, 2021
2 parents 69c8107 + b99dcba commit 8f2d658
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 68 deletions.
8 changes: 6 additions & 2 deletions commodore/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from pathlib import Path
from typing import Iterable
from typing import Iterable, Optional

import click
import yaml
Expand Down Expand Up @@ -386,13 +386,15 @@ def inventory(config: Config, verbose):
help="Whether to allow missing classes when rendering the inventory. Defaults to true.",
)
@click.argument("global-config")
@click.argument("tenant-config", required=False)
@verbosity
@pass_config
# pylint: disable=too-many-arguments
def component_versions(
config: Config,
verbose,
global_config: str,
tenant_config: Optional[str],
output_format: str,
values: Iterable[str],
allow_missing_classes: bool,
Expand All @@ -402,7 +404,9 @@ def component_versions(
try:
components = extract_components(
config,
InventoryFacts(global_config, None, extra_values, allow_missing_classes),
InventoryFacts(
global_config, tenant_config, extra_values, allow_missing_classes
),
)
except ValueError as e:
raise click.ClickException(f"While extracting components: {e}") from e
Expand Down
92 changes: 68 additions & 24 deletions commodore/inventory/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@

from commodore.cluster import Cluster, render_params
from commodore.component import component_parameters_key
from commodore.helpers import yaml_dump
from commodore.helpers import yaml_dump, yaml_load
from commodore.inventory import Inventory

FAKE_TENANT_ID = "t-foo"
FAKE_CLUSTER_ID = "c-bar"


class ClassNotFound(reclass_errors.ClassNotFound):
"""
Expand Down Expand Up @@ -69,6 +72,34 @@ def extra_class_files(self) -> Iterable[Path]:
def allow_missing_classes(self) -> bool:
return self._allow_missing_classes

@property
def tenant_id(self) -> str:
tenant_id = None
for f in self.extra_class_files:
fc = yaml_load(f)
tenant_id = (
fc.get("parameters", {}).get("cluster", {}).get("tenant", tenant_id)
)

if not tenant_id:
tenant_id = FAKE_TENANT_ID

return tenant_id

@property
def cluster_id(self) -> str:
cluster_id = None
for f in self.extra_class_files:
fc = yaml_load(f)
cluster_id = (
fc.get("parameters", {}).get("cluster", {}).get("name", cluster_id)
)

if not cluster_id:
cluster_id = FAKE_CLUSTER_ID

return cluster_id


class InventoryParameters:
def __init__(self, inv):
Expand Down Expand Up @@ -105,11 +136,9 @@ class DefaultsFact(Enum):


class InventoryFactory:
_FAKE_TENANT_ID = "t-foo"
_FAKE_CLUSTER_ID = "c-bar"

def __init__(self, work_dir: Path, global_dir: Path):
def __init__(self, work_dir: Path, global_dir: Path, tenant_dir: Optional[Path]):
self._global_dir = global_dir
self._tenant_dir = tenant_dir
self._inventory = Inventory(work_dir=work_dir)
self._distributions = self._find_values(DefaultsFact.DISTRIBUTION)
self._clouds = self._find_values(DefaultsFact.CLOUD)
Expand All @@ -134,6 +163,10 @@ def targets_dir(self) -> Path:
def global_dir(self) -> Path:
return self._global_dir

@property
def tenant_dir(self) -> Optional[Path]:
return self._tenant_dir

def _reclass_config(self, allow_missing_classes: bool) -> Dict:
return {
"storage_type": "yaml_fs",
Expand Down Expand Up @@ -175,8 +208,8 @@ def _render_inventory(

def reclass(self, invfacts: InventoryFacts) -> InventoryParameters:
cluster_response: Dict[str, Any] = {
"id": self._FAKE_CLUSTER_ID,
"tenant": self._FAKE_TENANT_ID,
"id": invfacts.cluster_id,
"tenant": invfacts.tenant_id,
"displayName": "Foo Inc. Bar cluster",
"facts": {
"distribution": "x-fake-distribution",
Expand All @@ -191,7 +224,7 @@ def reclass(self, invfacts: InventoryFacts) -> InventoryParameters:
cluster = Cluster(
cluster_response=cluster_response,
tenant_response={
"id": self._FAKE_TENANT_ID,
"id": invfacts.tenant_id,
"displayName": "Foo Inc.",
"gitRepo": {
"url": "not-a-real-repo",
Expand Down Expand Up @@ -220,12 +253,13 @@ def reclass(self, invfacts: InventoryFacts) -> InventoryParameters:
self.targets_dir / "global.yml",
)

# Create fake cluster class, this allows rendering the inventory with
# allow-missing-classes=False to work.
yaml_dump(
{},
self.classes_dir / self._FAKE_TENANT_ID / f"{self._FAKE_CLUSTER_ID}.yml",
)
if not invfacts.tenant_config:
# Create fake cluster class, this allows rendering the inventory with
# allow-missing-classes=False to work when no tenant is specified.
yaml_dump(
{},
self.classes_dir / FAKE_TENANT_ID / f"{FAKE_CLUSTER_ID}.yml",
)

return InventoryParameters(
self._render_inventory(
Expand Down Expand Up @@ -259,17 +293,27 @@ def cloud_regions(self) -> Dict[str, Iterable[str]]:
return self._cloud_regions

@classmethod
def _make_directories(cls, work_dir: Path):
os.makedirs(work_dir / "inventory" / "targets", exist_ok=True)
os.makedirs(
work_dir / "inventory" / "classes" / cls._FAKE_TENANT_ID, exist_ok=True
)

@classmethod
def from_repo_dir(cls, work_dir: Path, global_dir: Path, invfacts: InventoryFacts):
cls._make_directories(work_dir)
def from_repo_dirs(
cls,
work_dir: Path,
global_dir: Path,
tenant_dir: Optional[Path],
invfacts: InventoryFacts,
):
classes_dir = work_dir / "inventory" / "classes"
os.makedirs(work_dir / "inventory" / "targets", exist_ok=True)
os.makedirs(classes_dir, exist_ok=True)
if not tenant_dir:
os.makedirs(
work_dir / "inventory" / "classes" / FAKE_TENANT_ID, exist_ok=True
)
os.symlink(global_dir.absolute(), classes_dir / "global")
if tenant_dir:
os.symlink(tenant_dir.absolute(), classes_dir / invfacts.tenant_id)
for cf in invfacts.extra_class_files:
os.symlink(cf.absolute(), classes_dir / cf.name)
return InventoryFactory(work_dir=work_dir, global_dir=classes_dir / "global")
return InventoryFactory(
work_dir=work_dir,
global_dir=classes_dir / "global",
tenant_dir=classes_dir / invfacts.tenant_id,
)
29 changes: 18 additions & 11 deletions commodore/inventory/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
from .parameters import ClassNotFound, InventoryFactory, InventoryFacts


def _cleanup_work_dir(cfg: Config, work_dir: Path):
if not cfg.debug:
# Clean up work dir if we're not in debug mode
shutil.rmtree(work_dir)


def extract_components(
cfg: Config, invfacts: InventoryFacts
) -> Dict[str, Dict[str, str]]:
Expand All @@ -23,30 +29,31 @@ def extract_components(
)

global_dir = Path(invfacts.global_config).resolve().absolute()
tenant_dir = None
if invfacts.tenant_config:
raise NotImplementedError(
"Extracting component versions from tenant config not yet implemented"
)
work_dir = Path(tempfile.mkdtemp(prefix="renovate-reclass-")).resolve()
tenant_dir = Path(invfacts.tenant_config).resolve().absolute()

if global_dir.is_dir():
invfactory = InventoryFactory.from_repo_dir(work_dir, global_dir, invfacts)
work_dir = Path(tempfile.mkdtemp(prefix="commodore-reclass-")).resolve()

if global_dir.is_dir() and (not tenant_dir or tenant_dir.is_dir()):
invfactory = InventoryFactory.from_repo_dirs(
work_dir, global_dir, tenant_dir, invfacts
)
else:
raise NotImplementedError("Cloning the inventory first not yet implemented")
_cleanup_work_dir(cfg, work_dir)
raise NotImplementedError("Cloning global or tenant repo not yet implemented")

try:
inv = invfactory.reclass(invfacts)
components = inv.parameters("components")
except ClassNotFound as e:
print(e)
_cleanup_work_dir(cfg, work_dir)
raise ValueError(
"Unable to render inventory with `--no-allow-missing-classes`. "
+ f"Class '{e.name}' not found. "
+ "Verify the provided values or allow missing classes."
) from e

if not cfg.debug:
# Clean up work dir if we're not in debug mode
shutil.rmtree(work_dir)
_cleanup_work_dir(cfg, work_dir)

return components
22 changes: 19 additions & 3 deletions docs/modules/ROOT/pages/reference/commands.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,28 @@ The option `--alias` (or `-a`) can be used to compile an instance-aware componen

== Inventory Components

commodore inventory components GLOBAL_CONFIG
commodore inventory components GLOBAL_CONFIG [TENANT_CONFIG]

This command lists all the components defined in `parameters.components` in the Commodore hierarchy in directory `GLOBAL_CONFIG`.
If provided, the command also takes into account the tenant repo in directory `TENANT_CONFIG`.

NOTE: The command doesn't currently support cloning either the global or tenant repository from a Git repo URL.

The component takes a repeatable argument `-f / --values` which allows the user to specify additional files that should be used as classes when rendering the contents of `parameters.components`.

The command supports both YAML and JSON output.
When providing a tenant repo, users must specify the tenant ID and cluster ID for which the inventory should be rendered in a value class to obtain accurate results.
See a sample `cluster-info.yaml` which can be used for this purpose below.

.cluster-info.yaml
[source,yaml]
----
parameters:
cluster:
name: c-cluster-id-1234 <1>
tenant: t-tenant-id-1234 <2>
----
<1> Specify the cluster ID for which the inventory should be rendered
<2> Specify the tenant ID.
This must match the tenant ID associated with the provided tenant repo for accurate results.

NOTE: The command doesn't yet support taking into account a tenant repo when rendering the contents of `parameters.components`.
The command supports both YAML and JSON output.
Loading

0 comments on commit 8f2d658

Please sign in to comment.