nyl 0.0.3__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. nyl-0.0.4/PKG-INFO +31 -0
  2. nyl-0.0.4/README.md +9 -0
  3. {nyl-0.0.3 → nyl-0.0.4}/pyproject.toml +5 -1
  4. nyl-0.0.4/src/nyl/__init__.py +3 -0
  5. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/commands/__init__.py +26 -1
  6. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/commands/template.py +49 -11
  7. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/profiles/__init__.py +30 -10
  8. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/profiles/kubeconfig.py +24 -22
  9. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/profiles/tunnel.py +3 -3
  10. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/project/config.py +9 -0
  11. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/resources/applyset.py +13 -0
  12. nyl-0.0.4/src/nyl/tools/shell.py +6 -0
  13. nyl-0.0.3/PKG-INFO +0 -158
  14. nyl-0.0.3/README.md +0 -136
  15. nyl-0.0.3/tests/__init__.py +0 -0
  16. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/commands/crds.py +0 -0
  17. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/commands/new.py +0 -0
  18. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/commands/profile.py +0 -0
  19. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/commands/secrets.py +0 -0
  20. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/commands/tun.py +0 -0
  21. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/generator/__init__.py +0 -0
  22. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/generator/dispatch.py +0 -0
  23. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/generator/dispatch_test.py +0 -0
  24. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/generator/helmchart.py +0 -0
  25. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/generator/statefulsecret.py +0 -0
  26. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/profiles/config.py +0 -0
  27. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/resources/__init__.py +0 -0
  28. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/resources/applyset_test.py +0 -0
  29. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/resources/helmchart.py +0 -0
  30. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/resources/statefulsecret.py +0 -0
  31. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/secrets/__init__.py +0 -0
  32. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/secrets/config.py +0 -0
  33. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/secrets/null.py +0 -0
  34. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/secrets/sops.py +0 -0
  35. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/secrets/sops_test.py +0 -0
  36. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/tools/fs.py +0 -0
  37. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/tools/kubectl.py +0 -0
  38. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/tools/kvstore.py +0 -0
  39. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/tools/typer.py +0 -0
  40. {nyl-0.0.3 → nyl-0.0.4}/src/nyl/tools/types.py +0 -0
  41. {nyl-0.0.3/src/nyl → nyl-0.0.4/tests}/__init__.py +0 -0
nyl-0.0.4/PKG-INFO ADDED
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.1
2
+ Name: nyl
3
+ Version: 0.0.4
4
+ Summary: Default template for PDM package
5
+ Author-Email: Niklas Rosenstein <rosensteinniklas@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: bcrypt>=4.2.0
9
+ Requires-Dist: databind>=4.5.2
10
+ Requires-Dist: filelock>=3.15.4
11
+ Requires-Dist: jinja2>=3.1.4
12
+ Requires-Dist: kubernetes>=30.1.0
13
+ Requires-Dist: loguru>=0.7.2
14
+ Requires-Dist: nr-stream>=1.1.5
15
+ Requires-Dist: pyyaml>=6.0.1
16
+ Requires-Dist: requests>=2.32.3
17
+ Requires-Dist: stablehash>=0.2.0
18
+ Requires-Dist: structured-templates<0.1.0,>=0.0.5
19
+ Requires-Dist: typer>=0.12.3
20
+ Requires-Dist: typing-extensions>=4.12.2
21
+ Description-Content-Type: text/markdown
22
+
23
+ # nyl
24
+
25
+ Nyl is a versatile tool for generating Kubernetes manifests from a simple YAML configuration, encouraging
26
+ consistent and reusable deployment configurations, project layouts and operational workflows.
27
+
28
+ ## Local development
29
+
30
+ This project uses the [PDM](https://pdm-project.org/latest/) package manager for Python. Start with
31
+ `$ pipx install pdm` followed by `$ pdm install --frozen-lockfile`.
nyl-0.0.4/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # nyl
2
+
3
+ Nyl is a versatile tool for generating Kubernetes manifests from a simple YAML configuration, encouraging
4
+ consistent and reusable deployment configurations, project layouts and operational workflows.
5
+
6
+ ## Local development
7
+
8
+ This project uses the [PDM](https://pdm-project.org/latest/) package manager for Python. Start with
9
+ `$ pipx install pdm` followed by `$ pdm install --frozen-lockfile`.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nyl"
3
- version = "0.0.3"
3
+ version = "0.0.4"
4
4
  description = "Default template for PDM package"
5
5
  authors = [
6
6
  { name = "Niklas Rosenstein", email = "rosensteinniklas@gmail.com" },
@@ -37,9 +37,13 @@ build-backend = "pdm.backend"
37
37
 
38
38
  [tool.pdm]
39
39
  distribution = true
40
+ source = [
41
+ { name = "pypi", url = "https://pypi.org/simple" },
42
+ ]
40
43
 
41
44
  [tool.pdm.scripts]
42
45
  lint = "bash -c 'ruff format && ruff check --fix && mypy src' && pytest"
46
+ docs = "bash -c 'cd docs && mkdocs serve'"
43
47
 
44
48
  [tool.pdm.dev-dependencies]
45
49
  dev = [
@@ -0,0 +1,3 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("nyl")
@@ -3,6 +3,8 @@ Nyl is a flexible configuration management tool for Kubernetes resources that ca
3
3
  applications directly or integrate as an ArgoCD ConfigManagementPlugin.
4
4
  """
5
5
 
6
+ from pathlib import Path
7
+ from nyl import __version__
6
8
  from enum import Enum
7
9
  import sys
8
10
  from loguru import logger
@@ -26,6 +28,12 @@ app.add_typer(secrets.app)
26
28
  app.add_typer(tun.app)
27
29
 
28
30
 
31
+ LOG_TIME_FORMAT = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>"
32
+ LOG_LEVEL_FORAMT = "<level>{level: <8}</level>"
33
+ LOG_DETAILS_FORMAT = "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan>"
34
+ LOG_MESSAGE_FORMAT = "<level>{message}</level>"
35
+
36
+
29
37
  class LogLevel(str, Enum):
30
38
  TRACE = "trace"
31
39
  DEBUG = "debug"
@@ -38,6 +46,23 @@ class LogLevel(str, Enum):
38
46
  @app.callback()
39
47
  def _callback(
40
48
  log_level: LogLevel = Option(LogLevel.INFO, "--log-level", "-l", help="The log level to use."),
49
+ log_details: bool = Option(False, help="Include logger- and function names in the log message format."),
41
50
  ) -> None:
51
+ if log_details:
52
+ fmt = f"{LOG_TIME_FORMAT} | {LOG_LEVEL_FORAMT} | {LOG_DETAILS_FORMAT} | {LOG_MESSAGE_FORMAT}"
53
+ else:
54
+ fmt = f"{LOG_TIME_FORMAT} | {LOG_LEVEL_FORAMT} | {LOG_MESSAGE_FORMAT}"
55
+
42
56
  logger.remove()
43
- logger.add(sys.stderr, level=log_level.name)
57
+ logger.add(sys.stderr, level=log_level.name, format=fmt)
58
+ logger.opt(ansi=True).debug("Nyl v{} run from <yellow>{}</>.", __version__, Path.cwd())
59
+
60
+
61
+ @app.command()
62
+ def version() -> None:
63
+ """
64
+ Print the version of Nyl.
65
+ """
66
+
67
+ print(f"Nyl v{__version__}")
68
+ sys.exit(0)
@@ -1,7 +1,7 @@
1
1
  import atexit
2
2
  from dataclasses import dataclass
3
3
  from pathlib import Path
4
- from typing import cast
4
+ from typing import Optional, cast
5
5
  from loguru import logger
6
6
  from structured_templates import TemplateEngine
7
7
  from typer import Argument, Option
@@ -20,6 +20,8 @@ from nyl.tools.types import Manifest, Manifests
20
20
 
21
21
  from . import app
22
22
 
23
+ DEFAULT_PROFILE = "default"
24
+
23
25
 
24
26
  @dataclass
25
27
  class ManifestsWithSource:
@@ -34,7 +36,7 @@ class ManifestsWithSource:
34
36
  @app.command()
35
37
  def template(
36
38
  paths: list[Path] = Argument(..., help="The YAML file(s) to render. Can be a directory."),
37
- profile: str = Option("default", envvar="NYL_PROFILE", help="The Nyl profile to use."),
39
+ profile: Optional[str] = Option(None, envvar="NYL_PROFILE", help="The Nyl profile to use."),
38
40
  in_cluster: bool = Option(
39
41
  False, help="Use the in-cluster Kubernetes configuration. The --profile option is ignored."
40
42
  ),
@@ -68,10 +70,18 @@ def template(
68
70
  logger.info("Using in-cluster configuration.")
69
71
  load_incluster_config()
70
72
  else:
71
- with ProfileManager.load() as profiles:
72
- active = profiles.activate_profile(profile)
73
- logger.info(f"Using profile '{profile}' with kubeconfig '{active.kubeconfig}'.")
74
- load_kube_config(str(active.kubeconfig))
73
+ with ProfileManager.load(required=False) as profiles:
74
+ # If no profile to activate is specified, and there are no profiles defined, we're not activating a
75
+ # a profile. It should be valid to use Nyl without a `nyl-profiles.yaml` file.
76
+ if profile is not None or profiles.config.profiles:
77
+ profile = profile or DEFAULT_PROFILE
78
+ active = profiles.activate_profile(profile)
79
+ load_kube_config(str(active.kubeconfig))
80
+ else:
81
+ logger.opt(ansi=True).info(
82
+ "No <yellow>nyl-profiles.yaml</> file found, using default kubeconfig and context."
83
+ )
84
+ load_kube_config()
75
85
 
76
86
  project = ProjectConfig.load()
77
87
  if project.file:
@@ -97,7 +107,7 @@ def template(
97
107
  )
98
108
 
99
109
  for source in load_manifests(paths):
100
- logger.info("Rendering manifests from {}", source.file)
110
+ logger.opt(ansi=True).info("Rendering manifests from <blue>{}</>.", source.file)
101
111
 
102
112
  source.manifests = cast(Manifests, template_engine.evaluate(source.manifests))
103
113
  source.manifests = reconcile_generator(
@@ -116,14 +126,32 @@ def template(
116
126
  namespaces.add(manifest["metadata"]["name"])
117
127
  elif ApplySet.matches(manifest):
118
128
  if applyset is not None:
119
- logger.error(
120
- "Multiple ApplySet resources defined in '{}', there can only be one per source.",
129
+ logger.opt(ansi=True).error(
130
+ "Multiple ApplySet resources defined in <yellow>{}</>, there can only be one per source.",
121
131
  source.file,
122
132
  )
123
133
  exit(1)
124
134
  applyset = ApplySet.load(manifest)
125
135
  source.manifests.remove(manifest)
126
136
 
137
+ if not applyset and project.config.automatic_applyset:
138
+ if len(namespaces) > 1:
139
+ logger.opt(ansi=True).error(
140
+ "Multiple namespaces defined in <yellow>{}</>, but automatic ApplySet generation is enabled. "
141
+ "There can only be one namespace per source in this case.",
142
+ source.file,
143
+ )
144
+ exit(1)
145
+ elif len(namespaces) == 1:
146
+ applyset_name = next(iter(namespaces))
147
+ else:
148
+ applyset_name = source.file.stem
149
+
150
+ logger.opt(ansi=True).info(
151
+ "Automatically creating ApplySet for <blue>{}</> (name: <magenta>{}</>).", source.file, applyset_name
152
+ )
153
+ applyset = ApplySet.new(applyset_name)
154
+
127
155
  if applyset is not None:
128
156
  applyset.set_group_kinds(source.manifests)
129
157
  # HACK: Kubectl 1.30 can't create the custom resource, so we need to create it. But it will also reject
@@ -134,7 +162,11 @@ def template(
134
162
 
135
163
  if apply:
136
164
  # We need to ensure that ApplySet parent object exists before invoking `kubectl apply --applyset=...`.
137
- logger.info("Kubectl-apply ApplySet resource '{}' from '{}'", applyset.reference, source.file)
165
+ logger.opt(ansi=True).info(
166
+ "Kubectl-apply ApplySet resource <yellow>{}</> from <cyan>{}</>.",
167
+ applyset.reference,
168
+ source.file,
169
+ )
138
170
  kubectl.apply(Manifests([applyset.dump()]), force_conflicts=True)
139
171
  else:
140
172
  print("---")
@@ -206,7 +238,13 @@ def load_manifests(paths: list[Path]) -> list[ManifestsWithSource]:
206
238
  for path in paths:
207
239
  if path.is_dir():
208
240
  for item in path.iterdir():
209
- if item.name.startswith("nyl-") or item.suffix != ".yaml" or not item.is_file():
241
+ if (
242
+ item.name.startswith("nyl-")
243
+ or item.name.startswith(".")
244
+ or item.name.startswith("_")
245
+ or item.suffix != ".yaml"
246
+ or not item.is_file()
247
+ ):
210
248
  continue
211
249
  files.append(item)
212
250
  else:
@@ -42,12 +42,16 @@ class ProfileManager:
42
42
  tunnels: TunnelManager
43
43
  kubeconfig: KubeconfigManager
44
44
 
45
+ def __post_init__(self) -> None:
46
+ # We don't need to enter the TunnelsManager context manager until we actually need to open a tunnel.
47
+ self._tunnels_entered: bool = False
48
+
45
49
  def __enter__(self) -> "ProfileManager":
46
- self.tunnels.__enter__()
47
50
  return self
48
51
 
49
52
  def __exit__(self, *args: Any) -> None:
50
- self.tunnels.__exit__(*args)
53
+ if self._tunnels_entered:
54
+ self.tunnels.__exit__(*args)
51
55
 
52
56
  def activate_profile(self, profile_name: str, update_process_env: bool = True) -> ActivatedProfile:
53
57
  """
@@ -59,10 +63,18 @@ class ProfileManager:
59
63
  point to the activated profile's Kubeconfig file.
60
64
  """
61
65
 
66
+ logger.opt(ansi=True).info("Activating profile <magenta>{}</>...", profile_name)
67
+
62
68
  profile = self.config.profiles[profile_name]
69
+
63
70
  raw_kubeconfig = self.kubeconfig.get_raw_kubeconfig(profile_name, profile.kubeconfig)
64
71
 
65
72
  if profile.tunnel:
73
+ # If the tunnel manager has not been entered yet, do so now.
74
+ if not self._tunnels_entered:
75
+ self.tunnels.__enter__()
76
+ self._tunnels_entered = True
77
+
66
78
  forwardings = {"kubernetes": f"{raw_kubeconfig.api_host}:{raw_kubeconfig.api_port}"}
67
79
  assert self.config.file is not None, "Profile configuration file must be set."
68
80
  tun_spec = get_tunnel_spec(self.config.file, profile_name, profile.tunnel)
@@ -80,10 +92,6 @@ class ProfileManager:
80
92
  tun_description = ""
81
93
  timeout = 2
82
94
 
83
- api_server = f"https://{raw_kubeconfig.api_host}:{raw_kubeconfig.api_port}"
84
- logger.debug("Checking for API server connectivity ({}{})", api_server, tun_description)
85
- _wait_for_api_server(api_server, timeout)
86
-
87
95
  activated_profile = ActivatedProfile(
88
96
  kubeconfig=self.kubeconfig.get_updated_kubeconfig(
89
97
  profile_name=profile_name,
@@ -94,6 +102,10 @@ class ProfileManager:
94
102
  )
95
103
  )
96
104
 
105
+ api_server = f"https://{raw_kubeconfig.api_host}:{raw_kubeconfig.api_port}"
106
+ logger.opt(ansi=True).info("Waiting for API server connectivity (<blue>{}{}</>)", api_server, tun_description)
107
+ _wait_for_api_server(api_server, timeout)
108
+
97
109
  if update_process_env:
98
110
  logger.trace("Updating process environment with activated profile: {}", activated_profile.env)
99
111
  os.environ.update(activated_profile.env)
@@ -101,15 +113,23 @@ class ProfileManager:
101
113
  return activated_profile
102
114
 
103
115
  @staticmethod
104
- def load() -> "ProfileManager":
116
+ def load(required: bool = True) -> "ProfileManager":
105
117
  """
106
118
  Load the profile manager from the default configuration file.
119
+
120
+ Args:
121
+ required: Passed on to [`ProfileConfig.load()`]. If it is set to `False`, the Nyl state directory
122
+ is assumed relative to the current working directory (`./.nyl`).
107
123
  """
108
124
 
109
- config = ProfileConfig.load()
110
- assert config.file is not None, "Profile configuration file must be set."
125
+ config = ProfileConfig.load(required=required)
126
+ context_dir = config.file.parent if config.file else Path.cwd()
127
+
111
128
  tunnels = TunnelManager()
112
- kubeconfig = KubeconfigManager(cwd=config.file.parent, state_dir=config.file.with_name(".nyl") / "profiles")
129
+ kubeconfig = KubeconfigManager(
130
+ cwd=context_dir,
131
+ state_dir=context_dir / ".nyl" / "profiles",
132
+ )
113
133
  return ProfileManager(config, tunnels, kubeconfig)
114
134
 
115
135
 
@@ -1,10 +1,13 @@
1
1
  from dataclasses import dataclass
2
2
  import os
3
3
  from pathlib import Path
4
+ import shlex
4
5
  import subprocess
5
6
  from typing import Any
6
7
  from urllib.parse import urlparse
7
8
  import yaml
9
+
10
+ from nyl.tools.shell import pretty_cmd
8
11
  from .config import KubeconfigFromSsh, LocalKubeconfig
9
12
  from loguru import logger
10
13
 
@@ -55,42 +58,41 @@ class KubeconfigManager:
55
58
  match source:
56
59
  case LocalKubeconfig():
57
60
  raw_kubeconfig = Path(os.getenv("KUBECONFIG", os.path.expanduser("~/.kube/config")))
58
- if Path(raw_kubeconfig).exists():
61
+ if not raw_kubeconfig.exists():
59
62
  raise FileNotFoundError(f"Kubeconfig file '{raw_kubeconfig}' does not exist.")
60
- logger.info("Using local Kubeconfig file '{}'.", raw_kubeconfig)
63
+ logger.opt(ansi=True).info("Using kubeconfig <yellow>{}</>.", raw_kubeconfig)
61
64
 
62
65
  case KubeconfigFromSsh():
63
66
  raw_kubeconfig = self._state_dir / profile_name / "kubeconfig.orig"
64
- if not Path(raw_kubeconfig).exists() or force_refresh:
65
- logger.info(
66
- "Fetching Kubeconfig via SSH ({}@{}:{}).",
67
- source.user,
68
- source.host,
69
- source.path,
70
- )
71
- # Fetch the Kubeconfig file.
67
+
68
+ command = [
69
+ "ssh",
70
+ f"{source.user}@{source.host}",
71
+ f"cat {shlex.quote(source.path)}",
72
+ ]
73
+ if source.identity_file:
74
+ command[1:1] = ["-i", str(self._cwd / source.identity_file)]
75
+
76
+ # Fetch the Kubeconfig file.
77
+ if not raw_kubeconfig.exists() or force_refresh:
78
+ logger.opt(ansi=True).info("Fetching kubeconfig with <yellow>$ {}</>.", pretty_cmd(command))
72
79
  raw_kubeconfig.parent.mkdir(parents=True, exist_ok=True)
73
- command = [
74
- "ssh",
75
- f"{source.user}@{source.host}",
76
- "cat",
77
- source.path,
78
- ]
79
- if source.identity_file:
80
- command[1:1] = ["-i", str(self._cwd / source.identity_file)]
81
80
  kubeconfig_content = subprocess.check_output(command, text=True)
82
81
  Path(raw_kubeconfig).write_text(kubeconfig_content)
83
82
  else:
84
- logger.debug("Reusing cached Kubeconfig ({}).", raw_kubeconfig)
83
+ logger.opt(ansi=True).info(
84
+ "Using <u>cached</> kubeconfig from <yellow>$ {}</>.", pretty_cmd(command)
85
+ )
85
86
  case _:
86
87
  raise ValueError(f"Unsupported Kubeconfig type: {source.__class__.__name__}")
87
88
 
88
89
  # Find the Kubernetes API host and port.
89
90
  kubeconfig_data = yaml.safe_load(raw_kubeconfig.read_text())
90
91
  kubeconfig_data = _trim_to_context(kubeconfig_data, source.context)
91
- server = kubeconfig_data["clusters"][0]["cluster"]["server"]
92
- api_host = urlparse(server).hostname
93
- api_port = urlparse(server).port or 443
92
+ server: str = kubeconfig_data["clusters"][0]["cluster"]["server"]
93
+ api_host, api_port = (lambda parsed: (parsed.hostname, parsed.port or 443))(urlparse(server))
94
+ if api_host is None:
95
+ raise ValueError(f"no hostname in Kubeconfig server URL: {server!r}")
94
96
 
95
97
  raw_kubeconfig.chmod(0o600)
96
98
  return GetRawKubeconfigResult(
@@ -1,4 +1,3 @@
1
- import shlex
2
1
  from dataclasses import dataclass
3
2
  import os
4
3
  import random
@@ -10,6 +9,7 @@ from loguru import logger
10
9
 
11
10
  from stablehash import stablehash
12
11
  from nyl.tools.kvstore import JsonFileKvStore, SerializingStore
12
+ from nyl.tools.shell import pretty_cmd
13
13
 
14
14
 
15
15
  @dataclass
@@ -169,7 +169,7 @@ class TunnelManager:
169
169
  # Check if the tunnel is open and up-to-date.
170
170
  spec_hash = stablehash(spec).hexdigest()
171
171
  if status is not None and status.status == "open" and status.spec_hash == spec_hash:
172
- logger.debug("Tunnel for '{}' is already open.", spec.locator)
172
+ logger.opt(ansi=True).info("Reusing existing SSH tunnel.", spec.locator)
173
173
  return status
174
174
 
175
175
  # Close the tunnel if it is open.
@@ -206,7 +206,7 @@ class TunnelManager:
206
206
  if spec.identity_file is not None:
207
207
  ssh_args.extend(["-i", spec.identity_file])
208
208
 
209
- logger.debug("Opening SSH tunnel for '{}': $ {}", spec.locator, " ".join(map(shlex.quote, ssh_args)))
209
+ logger.opt(ansi=True).info("Opening SSH tunnel with <yellow>$ {}</>.", pretty_cmd(ssh_args))
210
210
  proc = subprocess.Popen(ssh_args)
211
211
 
212
212
  # Update the status.
@@ -12,6 +12,15 @@ class Project:
12
12
  Configuration for a Nyl project that is stored in a `nyl-project.toml` file.
13
13
  """
14
14
 
15
+ automatic_applyset: bool = True
16
+ """
17
+ Automatically generate an ApplySet for every template file. The applyset will be named after the template file,
18
+ unless there is namespace defined in the template file, in which case the applyset will be named after the
19
+ namespace.
20
+
21
+ This option is turned on by default. Using Nyl without a `nyl-project.toml` file will have this option enabled.
22
+ """
23
+
15
24
  search_path: list[Path] = field(default_factory=list)
16
25
  """
17
26
  Search path for additional resources used by the project. Used for example when using the `chart.path` option on a
@@ -194,6 +194,19 @@ class ApplySet(NylResource, api_version=API_VERSION_K8S):
194
194
  if self.contains_group_kinds is None:
195
195
  raise ValueError(f"ApplySet resource must have a {APPLYSET_ANNOTATION_CONTAINS_GROUP_KINDS!r} annotation")
196
196
 
197
+ @staticmethod
198
+ def new(name: str) -> "ApplySet":
199
+ """
200
+ Create a new ApplySet resource with the specified name.
201
+ """
202
+
203
+ return ApplySet(
204
+ metadata=ObjectMetadata(
205
+ name=name,
206
+ namespace=None,
207
+ )
208
+ )
209
+
197
210
 
198
211
  def calculate_applyset_id(*, name: str, namespace: str = "", group: str) -> str:
199
212
  """
@@ -0,0 +1,6 @@
1
+ from collections.abc import Iterable
2
+ import shlex
3
+
4
+
5
+ def pretty_cmd(command: Iterable[str]) -> str:
6
+ return " ".join(map(shlex.quote, command))
nyl-0.0.3/PKG-INFO DELETED
@@ -1,158 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: nyl
3
- Version: 0.0.3
4
- Summary: Default template for PDM package
5
- Author-Email: Niklas Rosenstein <rosensteinniklas@gmail.com>
6
- License: MIT
7
- Requires-Python: >=3.10
8
- Requires-Dist: bcrypt>=4.2.0
9
- Requires-Dist: databind>=4.5.2
10
- Requires-Dist: filelock>=3.15.4
11
- Requires-Dist: jinja2>=3.1.4
12
- Requires-Dist: kubernetes>=30.1.0
13
- Requires-Dist: loguru>=0.7.2
14
- Requires-Dist: nr-stream>=1.1.5
15
- Requires-Dist: pyyaml>=6.0.1
16
- Requires-Dist: requests>=2.32.3
17
- Requires-Dist: stablehash>=0.2.0
18
- Requires-Dist: structured-templates<0.1.0,>=0.0.5
19
- Requires-Dist: typer>=0.12.3
20
- Requires-Dist: typing-extensions>=4.12.2
21
- Description-Content-Type: text/markdown
22
-
23
- # nyl
24
-
25
- Nyl is a versatile Kubernetes resource management tool that fulfills various functions to enable reusable
26
- deployment configurations.
27
-
28
- __Features__
29
-
30
- * Generate Kubernetes manifests from a simple YAML configuration with templating support.
31
- * Inject secrets into Kubernetes manifests as time of generation.
32
- * Bootstrap a cluster and transfer control to ArgoCD or use it to manage a cluster from CI/CD or locally.
33
- * Connect to a Kubernetes cluster via an SSH tunnel.
34
-
35
- __Roadmap__
36
-
37
- * Integrate as an ArgoCD ConfigManagementPlugin.
38
- * Diffing
39
- * How to work around fields owned by a different field manager (e.g. MetalLB CRD `conversion..caBundle`) or `Job` resources?
40
-
41
- ## Concepts
42
-
43
- ### Packages
44
-
45
- A package is similar to a Helm chart in that it is a source for Kubernetes resources that can make use of
46
- templating to conditionally render resources and inject values into them. Resources generated by packages
47
- may make use of Nyl-specific resource kinds that are only available time of resource generation (see the
48
- [Templating > Resource Kinds](#resource-kinds) section below).
49
-
50
- Nyl packages can be checked into a source repository in a DRY form, but can be compiled to a bundle (e.g.
51
- including other referenced packages or Helm charts) for deployment when needed (e.g. for shipping to an
52
- air-gapped environment).
53
-
54
- Packages may have a `nyl-package.yaml` file that defines a schema for the package parameters and additional
55
- metadata. A package without this file may still accept and use parameters in its templates.
56
-
57
- ### Applications
58
-
59
- An application is an instance of one package that is deployed to a Kubernetes cluster and are in turn defined
60
- also in a Nyl package. Packages instantiated as applications may not produce other applications. Applications
61
- are usually accompanied by a `nyl-deployment.yaml` file that defines the top-level templating context for the
62
- package(s), such as the secret store.
63
-
64
- When deploying a package as an application, the package must not generated resources other than applications,
65
- as all deployed resources must be owned by an application.
66
-
67
- ### Deployment
68
-
69
- A deployment is where things get together: This is the place where you define the applications that should be
70
- deployed to a Kubernetes cluster, which cluster they are being deployed to, as well as sourcing secrets from
71
- a secret store. The configuration for a deployment is defined in a `nyl-deployment.yaml` file.
72
-
73
- ```yaml
74
- apiVersion: nyl/v1
75
- kind: Deployment
76
- spec:
77
- secretStores:
78
- default:
79
- type: Sops
80
- path: secrets.yaml
81
- ```
82
-
83
- __Spec__
84
-
85
- * `secrets` (array): The secret stores to make available in the templating context of the deployment. Any secrets
86
- need to be injected from these stores into the application values at templating time.
87
-
88
- ## Templating
89
-
90
- [Jinja2]: https://jinja.palletsprojects.com/en/3.0.x/
91
-
92
- Nyl uses [Jinja2] as a templating engine, and as such is slightly different from Helm.
93
-
94
- ### Injecting secrets
95
-
96
- Secrets are only available at the deployment level and need to be propagated further down.
97
-
98
- ```yaml
99
- apiVersion: nyl/v1
100
- kind: Application
101
- metadata:
102
- name: my-app
103
- spec:
104
- package: ./path/to/package
105
- values:
106
- theSecret: {{ Secrets.default.get("my-secret") }}
107
- ```
108
-
109
- ### Resource kinds
110
-
111
- At templating time, Nyl supports special resource kinds that will be expanded to more Kubernetes resources.
112
-
113
- #### `templating.nyl/v1/HelmChart`
114
-
115
- This resource can be used to instantiate Helm charts as part of a package.
116
-
117
- ```yaml
118
- apiVersion: templating.nyl/v1
119
- kind: HelmChart
120
- spec:
121
- repository: https://kubernetes.github.io/ingress-nginx
122
- chart: ingress-nginx
123
- version: 4.10.1
124
- releaseName: ingress-nginx
125
- releaseNamespace: ingress-nginx
126
- values: {}
127
- ```
128
-
129
- __Spec__
130
-
131
- * `repository` (string): The Helm repository URL, OCI registry URL (sans the chart name), a local path or a
132
- Git repository clone URL.
133
- * `chart` (string): The name of the chart in the repository, the chart name to append to the OCI registry URL,
134
- the name of the chart in the local path or the path to the chart in the Git repository.
135
- * `version` (string): The version of the chart to use. Only relevant for Helm/OCI repositories.
136
- * `releaseName` (string): The name of the Helm release.
137
- * `releaseNamespace` (string): The namespace to install the Helm release into. If any resources generated by the
138
- chart have no `metadata.namespace` field, it will be set to this value.
139
- * `values` (object): The values to pass to the Helm chart.
140
-
141
- #### `templating.nyl/v1/Package`
142
-
143
- Use this resource to instantiate another package.
144
-
145
- ```yaml
146
- apiVersion: templating.nyl/v1
147
- kind: Package
148
- spec:
149
- package: ./path/to/package
150
- values: {}
151
- ```
152
-
153
- __Spec__
154
-
155
- * `package` (string): The path to the package to instantiate. Must be prefixed with `./` to be resolved relative
156
- to the package that references it. Otherwise, it will be resolved in a search path that is defined in the
157
- `nyl-deployment.yaml` file.
158
- * `values` (object): The values to pass to the package.
nyl-0.0.3/README.md DELETED
@@ -1,136 +0,0 @@
1
- # nyl
2
-
3
- Nyl is a versatile Kubernetes resource management tool that fulfills various functions to enable reusable
4
- deployment configurations.
5
-
6
- __Features__
7
-
8
- * Generate Kubernetes manifests from a simple YAML configuration with templating support.
9
- * Inject secrets into Kubernetes manifests as time of generation.
10
- * Bootstrap a cluster and transfer control to ArgoCD or use it to manage a cluster from CI/CD or locally.
11
- * Connect to a Kubernetes cluster via an SSH tunnel.
12
-
13
- __Roadmap__
14
-
15
- * Integrate as an ArgoCD ConfigManagementPlugin.
16
- * Diffing
17
- * How to work around fields owned by a different field manager (e.g. MetalLB CRD `conversion..caBundle`) or `Job` resources?
18
-
19
- ## Concepts
20
-
21
- ### Packages
22
-
23
- A package is similar to a Helm chart in that it is a source for Kubernetes resources that can make use of
24
- templating to conditionally render resources and inject values into them. Resources generated by packages
25
- may make use of Nyl-specific resource kinds that are only available time of resource generation (see the
26
- [Templating > Resource Kinds](#resource-kinds) section below).
27
-
28
- Nyl packages can be checked into a source repository in a DRY form, but can be compiled to a bundle (e.g.
29
- including other referenced packages or Helm charts) for deployment when needed (e.g. for shipping to an
30
- air-gapped environment).
31
-
32
- Packages may have a `nyl-package.yaml` file that defines a schema for the package parameters and additional
33
- metadata. A package without this file may still accept and use parameters in its templates.
34
-
35
- ### Applications
36
-
37
- An application is an instance of one package that is deployed to a Kubernetes cluster and are in turn defined
38
- also in a Nyl package. Packages instantiated as applications may not produce other applications. Applications
39
- are usually accompanied by a `nyl-deployment.yaml` file that defines the top-level templating context for the
40
- package(s), such as the secret store.
41
-
42
- When deploying a package as an application, the package must not generated resources other than applications,
43
- as all deployed resources must be owned by an application.
44
-
45
- ### Deployment
46
-
47
- A deployment is where things get together: This is the place where you define the applications that should be
48
- deployed to a Kubernetes cluster, which cluster they are being deployed to, as well as sourcing secrets from
49
- a secret store. The configuration for a deployment is defined in a `nyl-deployment.yaml` file.
50
-
51
- ```yaml
52
- apiVersion: nyl/v1
53
- kind: Deployment
54
- spec:
55
- secretStores:
56
- default:
57
- type: Sops
58
- path: secrets.yaml
59
- ```
60
-
61
- __Spec__
62
-
63
- * `secrets` (array): The secret stores to make available in the templating context of the deployment. Any secrets
64
- need to be injected from these stores into the application values at templating time.
65
-
66
- ## Templating
67
-
68
- [Jinja2]: https://jinja.palletsprojects.com/en/3.0.x/
69
-
70
- Nyl uses [Jinja2] as a templating engine, and as such is slightly different from Helm.
71
-
72
- ### Injecting secrets
73
-
74
- Secrets are only available at the deployment level and need to be propagated further down.
75
-
76
- ```yaml
77
- apiVersion: nyl/v1
78
- kind: Application
79
- metadata:
80
- name: my-app
81
- spec:
82
- package: ./path/to/package
83
- values:
84
- theSecret: {{ Secrets.default.get("my-secret") }}
85
- ```
86
-
87
- ### Resource kinds
88
-
89
- At templating time, Nyl supports special resource kinds that will be expanded to more Kubernetes resources.
90
-
91
- #### `templating.nyl/v1/HelmChart`
92
-
93
- This resource can be used to instantiate Helm charts as part of a package.
94
-
95
- ```yaml
96
- apiVersion: templating.nyl/v1
97
- kind: HelmChart
98
- spec:
99
- repository: https://kubernetes.github.io/ingress-nginx
100
- chart: ingress-nginx
101
- version: 4.10.1
102
- releaseName: ingress-nginx
103
- releaseNamespace: ingress-nginx
104
- values: {}
105
- ```
106
-
107
- __Spec__
108
-
109
- * `repository` (string): The Helm repository URL, OCI registry URL (sans the chart name), a local path or a
110
- Git repository clone URL.
111
- * `chart` (string): The name of the chart in the repository, the chart name to append to the OCI registry URL,
112
- the name of the chart in the local path or the path to the chart in the Git repository.
113
- * `version` (string): The version of the chart to use. Only relevant for Helm/OCI repositories.
114
- * `releaseName` (string): The name of the Helm release.
115
- * `releaseNamespace` (string): The namespace to install the Helm release into. If any resources generated by the
116
- chart have no `metadata.namespace` field, it will be set to this value.
117
- * `values` (object): The values to pass to the Helm chart.
118
-
119
- #### `templating.nyl/v1/Package`
120
-
121
- Use this resource to instantiate another package.
122
-
123
- ```yaml
124
- apiVersion: templating.nyl/v1
125
- kind: Package
126
- spec:
127
- package: ./path/to/package
128
- values: {}
129
- ```
130
-
131
- __Spec__
132
-
133
- * `package` (string): The path to the package to instantiate. Must be prefixed with `./` to be resolved relative
134
- to the package that references it. Otherwise, it will be resolved in a search path that is defined in the
135
- `nyl-deployment.yaml` file.
136
- * `values` (object): The values to pass to the package.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes