Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/content/reference/templating/inlining/postprocessor.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,6 @@ With the Nyl `PostProcessor`, you can apply Kyverno policies to validate or muta
template: *podSpec
```

Running `nyl template forgejo.yaml` will use the `kyverno` CLI to apply the policy to the manifests generated by
the Helm chart. Note that the post processing happens at the very end after all other Kubernetes manifests have
Running `nyl template forgejo.yaml` will use the `kyverno` CLI to apply the policy to the resources generated by
the Helm chart. Note that the post processing happens at the very end after all other Kubernetes resources have
been generated.
4 changes: 2 additions & 2 deletions docs/content/reference/templating/secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ the desired configuration).

!!! todo

ArgoCD caches generated manifests so there may be a time delay between the secret update and ArgoCD fully
re-materilizing the desired manifests with the updated secret being taken into account. What's the cache TTL,
ArgoCD caches generated resources so there may be a time delay between the secret update and ArgoCD fully
re-materilizing the desired resources with the updated secret being taken into account. What's the cache TTL,
can it be changed/flushed?

(A "hard refresh" usually works, but for automatic drift reconcilation when secrets update, having a lower TTL
Expand Down
12 changes: 6 additions & 6 deletions src/nyl/commands/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ def template(
),
apply: bool = Option(
False,
help="Run `kubectl apply` on the rendered manifests, once for each source file. "
help="Run `kubectl apply` on the rendered resources, once for each source file. "
"Implies `--no-applyset-part-of`. When an ApplySet is defined in the source file, it will be applied "
"separately. Note that this option implies `kubectl --prune`.",
),
diff: bool = Option(
False,
help="Run `kubectl diff` on the rendered manifests, once for each source file. Cannot be combined with "
help="Run `kubectl diff` on the rendered resources, once for each source file. Cannot be combined with "
"`--apply`. Note that this does not generally ",
),
generate_applysets: Optional[bool] = Option(
Expand All @@ -111,7 +111,7 @@ def template(
applyset_part_of: bool = Option(
True,
help="Add the 'applyset.kubernetes.io/part-of' label to all resources belonging to an ApplySet (if declared). "
"This option must be disabled when passing the generated manifests to `kubectl apply --applyset=...`, as it "
"This option must be disabled when passing the generated resources to `kubectl apply --applyset=...`, as it "
"would otherwise cause an error due to the label being present on the input data.",
),
default_namespace: Optional[str] = Option(
Expand Down Expand Up @@ -175,7 +175,7 @@ def template(

if apply:
# When running with --apply, we must ensure that the --applyset-part-of option is disabled, as it would cause
# an error when passing the generated manifests to `kubectl apply --applyset=...`.
# an error when passing the generated resources to `kubectl apply --applyset=...`.
applyset_part_of = False

if apply and diff:
Expand Down Expand Up @@ -373,14 +373,14 @@ def worker() -> ResourceList:
if apply:
logger.info("Kubectl-apply {} resource(s) from '{}'", len(source.resources), source.file)
kubectl.apply(
manifests=source.resources,
resources=source.resources,
applyset=applyset.reference if applyset else None,
prune=True if applyset else False,
force_conflicts=True,
)
elif diff:
logger.info("Kubectl-diff {} resource(s) from '{}'", len(source.resources), source.file)
kubectl.diff(manifests=source.resources, applyset=applyset)
kubectl.diff(resources=source.resources, applyset=applyset)
else:
# If we're not going to be applying the resources immediately via `kubectl`, we print them to stdout.
for resource in source.resources:
Expand Down
4 changes: 2 additions & 2 deletions src/nyl/generator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
This package contains everything related to the generation of Kubernetes manifests via Nyl.
This package contains everything related to the generation of Kubernetes resources via Nyl.
"""

from abc import ABC, abstractmethod
Expand Down Expand Up @@ -29,7 +29,7 @@ def __init_subclass__(cls, resource_type: type[T], **kwargs: Any) -> None:
@abstractmethod
def generate(self, /, resource: T) -> ResourceList:
"""
Evaluate a Nyl resource and return a list of the generated Kubernetes manifests.
Evaluate a Nyl resource and return a list of the generated Kubernetes resources.
"""

raise NotImplementedError
Expand Down
2 changes: 1 addition & 1 deletion src/nyl/generator/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def default(
components_path: Path to search for Nyl components.
working_dir: The working directory to consider relative paths relative to.
client: The Kubernetes API client to use for interacting with the Kubernetes API.
kube_version: The Kubernetes API version to generate manifests for. If not specified, the version will be
kube_version: The Kubernetes API version to generate resources for. If not specified, the version will be
determined from the Kubernetes API server.
kube_api_versions: The Kubernetes API versions supported by the cluster. If not specified, the versions
will be determined from the Kubernetes API server.
Expand Down
4 changes: 2 additions & 2 deletions src/nyl/generator/helmchart.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class HelmChartGenerator(Generator[HelmChart], resource_type=HelmChart):

kube_version: str
"""
The Kubernetes API version to generate manifests for. This must be known for Helm cluster feature detection (such
The Kubernetes API version to generate resources for. This must be known for Helm cluster feature detection (such
as, for example, picking the right apiVersion for Ingress resources).
"""

Expand Down Expand Up @@ -198,7 +198,7 @@ def generate(self, /, res: HelmChart) -> ResourceList:
# command.append(f"{key}={json.dumps(value)}")

logger.opt(colors=True).debug(
"Generating manifests with Helm: $ <yellow>{}</>", " ".join(map(shlex.quote, command))
"Generating resources with Helm: $ <yellow>{}</>", " ".join(map(shlex.quote, command))
)
try:
result = subprocess.run(command, capture_output=True, check=True)
Expand Down
2 changes: 1 addition & 1 deletion src/nyl/profiles/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Profile:

values: dict[str, Any] = field(default_factory=dict)
"""
Global values that are accessible during manifest rendering under the `values` object.
Global values that are accessible during manifest file rendering under the `values` object.
"""

kubeconfig: LocalKubeconfig | KubeconfigFromSsh = field(default_factory=lambda: LocalKubeconfig())
Expand Down
50 changes: 25 additions & 25 deletions src/nyl/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ def __init_subclass__(cls, api_version: str, kind: str | None = None) -> None:
cls.KIND = kind or cls.__name__

@classmethod
def load(cls, manifest: Resource) -> "Self":
def load(cls, resource: Resource) -> "Self":
"""
Load a Nyl resource from a manifest. If called directly on `NylResource`, this will deserialize into the
appropriate subclass based on the `kind` field in the manifest. If the method is instead called on a subclass
directly, the subclass will be used to deserialize the manifest.
appropriate subclass based on the `kind` field in the resource. If the method is instead called on a subclass
directly, the subclass will be used to deserialize the resource.
"""

if manifest.get("apiVersion") not in (API_VERSION_K8S, API_VERSION_INLINE):
raise ValueError(f"Unsupported apiVersion: {manifest.get('apiVersion')!r}")
if resource.get("apiVersion") not in (API_VERSION_K8S, API_VERSION_INLINE):
raise ValueError(f"Unsupported apiVersion: {resource.get('apiVersion')!r}")

if cls is NylResource:
kind = manifest["kind"]
kind = resource["kind"]
module_name = __name__ + "." + kind.lower()
try:
module = __import__(module_name, fromlist=[kind])
Expand All @@ -61,32 +61,32 @@ def load(cls, manifest: Resource) -> "Self":
raise ValueError(f"Unsupported resource kind: {kind}")

else:
if manifest["kind"] != cls.KIND:
raise ValueError(f"Expected kind {cls.KIND!r}, got {manifest['kind']!r}")
if resource["kind"] != cls.KIND:
raise ValueError(f"Expected kind {cls.KIND!r}, got {resource['kind']!r}")
subcls = cls

manifest = Resource(manifest)
manifest.pop("apiVersion")
manifest.pop("kind")
resource = Resource(resource)
resource.pop("apiVersion")
resource.pop("kind")

return cast(Self, deser(manifest, subcls))
return cast(Self, deser(resource, subcls))

@classmethod
def maybe_load(cls, manifest: Resource) -> "Self | None":
def maybe_load(cls, resource: Resource) -> "Self | None":
"""
Maybe load the manifest into a NylResource if the `apiVersion` matches. If the resource kind is not supported,
Maybe load the resource into a NylResource if the `apiVersion` matches. If the resource kind is not supported,
an error will be raised. If this is called on a subclass of `NylResource`, the subclass's kind will also be
checked.
"""

if cls.matches(manifest):
return cls.load(manifest)
if cls.matches(resource):
return cls.load(resource)
return None

@classmethod
def matches(cls, manifest: Resource, apiVersion: str | Collection[str] | None = None) -> bool:
def matches(cls, resource: Resource, apiVersion: str | Collection[str] | None = None) -> bool:
"""
Check if the manifest is a NylResource of the correct `apiVersion` and possibly `kind` (if called on a
Check if the resource is a NylResource of the correct `apiVersion` and possibly `kind` (if called on a
`NylResource` subclass).
"""

Expand All @@ -95,23 +95,23 @@ def matches(cls, manifest: Resource, apiVersion: str | Collection[str] | None =
elif isinstance(apiVersion, str):
apiVersion = {apiVersion}

if manifest.get("apiVersion") not in apiVersion:
if resource.get("apiVersion") not in apiVersion:
return False

if cls is not NylResource and manifest["kind"] != cls.KIND:
if cls is not NylResource and resource["kind"] != cls.KIND:
return False

return True

def dump(self) -> Resource:
"""
Dump the resource to a manifest.
Dump the resource to a resource document.
"""

manifest = cast(Resource, ser(self, type(self), settings=[SerializeDefaults(False)]))
manifest["apiVersion"] = self.API_VERSION
manifest["kind"] = self.KIND
return Resource(manifest)
resource = cast(Resource, ser(self, type(self), settings=[SerializeDefaults(False)]))
resource["apiVersion"] = self.API_VERSION
resource["kind"] = self.KIND
return Resource(resource)


@dataclass
Expand Down
12 changes: 6 additions & 6 deletions src/nyl/resources/applyset.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class ApplySet(NylResource, api_version=API_VERSION_K8S):

Nyl's ApplySet resource is not namespaces.

When loading manifests from a file, Nyl looks for an ApplySet resource to determine if the manifests are to be
When loading manifests from a file, Nyl looks for an ApplySet resource to determine if the resources are to be
associated with an ApplySet.
"""

Expand Down Expand Up @@ -152,15 +152,15 @@ def contains_group_kinds(self, value: list[str]) -> None:
self.metadata.annotations = {}
self.metadata.annotations[APPLYSET_ANNOTATION_CONTAINS_GROUP_KINDS] = ",".join(sorted(value))

def set_group_kinds(self, manifests: ResourceList) -> None:
def set_group_kinds(self, resources: ResourceList) -> None:
"""
Set the kinds of resources that are part of the ApplySet based on the specified manifests.
Set the kinds of resources that are part of the ApplySet based on the specified resources.
"""

kinds = set()
for manifest in manifests:
if "kind" in manifest:
kinds.add(get_canonical_resource_kind_name(manifest["apiVersion"], manifest["kind"]))
for resource in resources:
if "kind" in resource:
kinds.add(get_canonical_resource_kind_name(resource["apiVersion"], resource["kind"]))
self.contains_group_kinds = list(kinds)

def validate(self) -> None:
Expand Down
12 changes: 6 additions & 6 deletions src/nyl/resources/helmchart_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ def test__HelmChartGenerator__generate__populates_namespace(
generator: HelmChartGenerator,
) -> None:
helmchart.metadata.namespace = None
manifests = generator.generate(helmchart)
assert len(manifests) == 1
assert manifests[0]["metadata"].get("namespace") is None
resources = generator.generate(helmchart)
assert len(resources) == 1
assert resources[0]["metadata"].get("namespace") is None

helmchart.metadata.namespace = "foo"
manifests = generator.generate(helmchart)
assert len(manifests) == 1
assert manifests[0]["metadata"].get("namespace") == "foo"
resources = generator.generate(helmchart)
assert len(resources) == 1
assert resources[0]["metadata"].get("namespace") == "foo"
12 changes: 6 additions & 6 deletions src/nyl/resources/postprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class KyvernoSpec:
inlinePolicies: dict[str, KyvernoPolicyDocument] = field(default_factory=dict)
"""
A mapping of policy name to the Kyverno policy document. Allows specifying Kyverno policies to be applied
to the generated manifests inline.
to the generated resources inline.
"""


Expand Down Expand Up @@ -94,7 +94,7 @@ def get_policy_files(self, name: str, workdir: Path, tmpdir: Path) -> list[Path]
@dataclass(kw_only=True)
class PostProcessor(NylResource, api_version=API_VERSION_INLINE):
"""
Configuration for post-processing Kubernetes manifests in a file. Note that the post-processing is always
Configuration for post-processing Kubernetes resources from a manifest file. Note that the post-processing is always
scoped to the file that the processor is defined in. Post processors will be applied after all inline resources
are reconciled.

Expand Down Expand Up @@ -124,7 +124,7 @@ def process(self, resources: ResourceList, source_file: Path) -> ResourceList:

if policy_files:
logger.info(
"Applying {} Kyverno {} to manifests from '{}': {}",
"Applying {} Kyverno {} to resources from '{}': {}",
len(policy_files),
"policy" if len(policy_files) == 1 else "policies",
source_file.name,
Expand Down Expand Up @@ -168,7 +168,7 @@ def apply_kyverno_policies(
manifest_file = tmp / "manifest.yaml"
manifest_file.write_text(yaml.safe_dump_all(resources))

# Create an output directory for Kyverno to write the mutated manifests to.
# Create an output directory for Kyverno to write the mutated resources to.
output_dir = tmp / "output"
output_dir.mkdir()

Expand All @@ -186,7 +186,7 @@ def apply_kyverno_policies(

if result.returncode != 0:
logger.error("Kyverno stdout:\n{}", result.stdout.decode())
raise RuntimeError("Kyverno failed to apply policies to manifests. See logs for more details")
raise RuntimeError("Kyverno failed to apply policies to resources. See logs for more details")
else:
logger.debug("Kyverno stdout:\n{}", result.stdout.decode())

Expand All @@ -195,7 +195,7 @@ def apply_kyverno_policies(
list(chain(*(filter(None, yaml.safe_load_all(file.read_text())) for file in output_dir.iterdir())))
)
if len(new_resources) != len(resources):
# Showing identifies for manifests that have been added or removed is not very helpful because
# Showing identifiers for resources that have been added or removed is not very helpful because
# Kyverno will add `namespace: default` to those without the field, which changes the identifier.
raise RuntimeError(
"Unexpected behaviour of `kyverno apply` command: The number of resources generated in the "
Expand Down
2 changes: 1 addition & 1 deletion src/nyl/templating.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class LookupResourceWrapper(Mapping[str, Any]):
"""
A wrapper for a Kubernetes resources returned by `lookup()` that permits looking up fields by `__getitem__()`
and `__getattr__()`. This wraps a `ResourceInstance` or `ResourceField`, which can later be treated by the
`NylTemplateEngine` to serialize into a dictionary when embedded into a manifest.
`NylTemplateEngine` to serialize into a dictionary when embedded into a resource.

This class is needed because the YAML serializer will not be able to serialize the `ResourceInstance` or
`ResourceField` objects returned by `lookup()`.
Expand Down
18 changes: 9 additions & 9 deletions src/nyl/tools/kubectl.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ def set_kubeconfig(self, kubeconfig: dict[str, Any] | str | Path) -> None:

def apply(
self,
manifests: ResourceList,
resources: ResourceList,
force_conflicts: bool = False,
server_side: bool = True,
applyset: str | None = None,
prune: bool = False,
) -> None:
"""
Apply the given manifests to the cluster.
Apply the given resources to the cluster.
"""

env = self.env
Expand All @@ -110,22 +110,22 @@ def apply(
if force_conflicts:
command.append("--force-conflicts")

logger.debug("Applying manifests with command: $ {command}", command=lazy_str(pretty_cmd, command))
status = subprocess.run(command, input=yaml.safe_dump_all(manifests), text=True, env={**os.environ, **env})
logger.debug("Applying resources with command: $ {command}", command=lazy_str(pretty_cmd, command))
status = subprocess.run(command, input=yaml.safe_dump_all(resources), text=True, env={**os.environ, **env})
if status.returncode:
raise KubectlError(status.returncode)

def diff(
self,
manifests: ResourceList,
resources: ResourceList,
applyset: ApplySet | None = None,
on_error: Literal["raise", "return"] = "raise",
) -> Literal["no-diff", "diff", "error"]:
"""
Diff the given manifests against the cluster.
Diff the given resources against the cluster.

Args:
manifests: The input manifests.
resources: The input resources.
on_error: What to do if the diff command fails. If "raise", raise a KubectlError. If "return", return the
status code.
applyset: The applyset to use for the diff. This can only be combined with the `prune` option.
Expand All @@ -143,8 +143,8 @@ def diff(
if match_labels:
command.extend(["-l", ",".join(f"{k}={v}" for k, v in match_labels.items())])

logger.debug("Diffing manifests with command: $ {command}", command=lazy_str(pretty_cmd, command))
status = subprocess.run(command, input=yaml.safe_dump_all(manifests), text=True)
logger.debug("Diffing resources with command: $ {command}", command=lazy_str(pretty_cmd, command))
status = subprocess.run(command, input=yaml.safe_dump_all(resources), text=True)
if status.returncode == 1:
return "diff"
elif status.returncode == 0:
Expand Down
Loading