nyl 0.1.1__tar.gz → 0.2.0__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 (102) hide show
  1. {nyl-0.1.1 → nyl-0.2.0}/PKG-INFO +1 -1
  2. {nyl-0.1.1 → nyl-0.2.0}/pyproject.toml +1 -1
  3. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/commands/__init__.py +2 -0
  4. nyl-0.2.0/src/nyl/commands/secrets.py +61 -0
  5. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/commands/template.py +53 -31
  6. nyl-0.2.0/src/nyl/commands/tools/__init__.py +13 -0
  7. nyl-0.2.0/src/nyl/commands/tools/bcrypt.py +17 -0
  8. nyl-0.1.1/src/nyl/commands/secrets.py → nyl-0.2.0/src/nyl/commands/tools/sops.py +9 -53
  9. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/resources/placeholder.py +10 -0
  10. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/templating.py +6 -0
  11. {nyl-0.1.1 → nyl-0.2.0}/.envrc +0 -0
  12. {nyl-0.1.1 → nyl-0.2.0}/.github/workflows/docker.yaml +0 -0
  13. {nyl-0.1.1 → nyl-0.2.0}/.github/workflows/docs.yaml +0 -0
  14. {nyl-0.1.1 → nyl-0.2.0}/.github/workflows/python.yaml +0 -0
  15. {nyl-0.1.1 → nyl-0.2.0}/.gitignore +0 -0
  16. {nyl-0.1.1 → nyl-0.2.0}/.kraken.py +0 -0
  17. {nyl-0.1.1 → nyl-0.2.0}/README.md +0 -0
  18. {nyl-0.1.1 → nyl-0.2.0}/argocd-cmp/Dockerfile +0 -0
  19. {nyl-0.1.1 → nyl-0.2.0}/argocd-cmp/README.md +0 -0
  20. {nyl-0.1.1 → nyl-0.2.0}/argocd-cmp/plugin.yaml +0 -0
  21. {nyl-0.1.1 → nyl-0.2.0}/argocd-with-nyl/.envrc +0 -0
  22. {nyl-0.1.1 → nyl-0.2.0}/argocd-with-nyl/.secrets.yaml +0 -0
  23. {nyl-0.1.1 → nyl-0.2.0}/argocd-with-nyl/.sops.yaml +0 -0
  24. {nyl-0.1.1 → nyl-0.2.0}/argocd-with-nyl/README.md +0 -0
  25. {nyl-0.1.1 → nyl-0.2.0}/argocd-with-nyl/argocd.yaml +0 -0
  26. {nyl-0.1.1 → nyl-0.2.0}/argocd-with-nyl/nyl-project.yaml +0 -0
  27. {nyl-0.1.1 → nyl-0.2.0}/argocd-with-nyl/nyl-secrets.yaml +0 -0
  28. {nyl-0.1.1 → nyl-0.2.0}/docs/content/index.md +0 -0
  29. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/applysets.md +0 -0
  30. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/argocd-plugin.md +0 -0
  31. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/cluster-connectivity.md +0 -0
  32. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/configuration/environment.md +0 -0
  33. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/configuration/profiles.md +0 -0
  34. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/configuration/projects.md +0 -0
  35. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/configuration/secrets.md +0 -0
  36. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/configuration-api/profiles.md +0 -0
  37. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/configuration-api/secrets.md +0 -0
  38. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/templating/basics.md +0 -0
  39. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/templating/components.md +0 -0
  40. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/templating/inlining.md +0 -0
  41. {nyl-0.1.1 → nyl-0.2.0}/docs/content/reference/templating/secrets.md +0 -0
  42. {nyl-0.1.1 → nyl-0.2.0}/docs/mkdocs.yml +0 -0
  43. {nyl-0.1.1 → nyl-0.2.0}/docs/pyproject.toml +0 -0
  44. {nyl-0.1.1 → nyl-0.2.0}/docs/uv.lock +0 -0
  45. {nyl-0.1.1 → nyl-0.2.0}/example/manifests/postgres.yaml +0 -0
  46. {nyl-0.1.1 → nyl-0.2.0}/example/nyl-profiles.yaml +0 -0
  47. {nyl-0.1.1 → nyl-0.2.0}/example/nyl-secrets.yaml +0 -0
  48. {nyl-0.1.1 → nyl-0.2.0}/example/secrets.yaml +0 -0
  49. {nyl-0.1.1 → nyl-0.2.0}/src/kubernetes/README.md +0 -0
  50. {nyl-0.1.1 → nyl-0.2.0}/src/kubernetes/__init__.pyi +0 -0
  51. {nyl-0.1.1 → nyl-0.2.0}/src/kubernetes/dynamic/__init__.pyi +0 -0
  52. {nyl-0.1.1 → nyl-0.2.0}/src/kubernetes/dynamic/client.pyi +0 -0
  53. {nyl-0.1.1 → nyl-0.2.0}/src/kubernetes/dynamic/discovery.pyi +0 -0
  54. {nyl-0.1.1 → nyl-0.2.0}/src/kubernetes/dynamic/exceptions.pyi +0 -0
  55. {nyl-0.1.1 → nyl-0.2.0}/src/kubernetes/dynamic/resource.pyi +0 -0
  56. {nyl-0.1.1 → nyl-0.2.0}/src/kubernetes/watch/__init__.pyi +0 -0
  57. {nyl-0.1.1 → nyl-0.2.0}/src/kubernetes/watch/watch.pyi +0 -0
  58. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/__init__.py +0 -0
  59. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/commands/argocd.py +0 -0
  60. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/commands/crds.py +0 -0
  61. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/commands/new.py +0 -0
  62. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/commands/profile.py +0 -0
  63. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/commands/run.py +0 -0
  64. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/commands/tun.py +0 -0
  65. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/generator/__init__.py +0 -0
  66. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/generator/components.py +0 -0
  67. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/generator/dispatch.py +0 -0
  68. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/generator/dispatch_test.py +0 -0
  69. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/generator/helmchart.py +0 -0
  70. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/generator/statefulsecret.py +0 -0
  71. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/profiles/__init__.py +0 -0
  72. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/profiles/config.py +0 -0
  73. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/profiles/config_test.py +0 -0
  74. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/profiles/kubeconfig.py +0 -0
  75. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/profiles/tunnel.py +0 -0
  76. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/project/config.py +0 -0
  77. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/resources/__init__.py +0 -0
  78. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/resources/applyset.py +0 -0
  79. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/resources/applyset_test.py +0 -0
  80. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/resources/helmchart.py +0 -0
  81. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/resources/statefulsecret.py +0 -0
  82. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/secrets/__init__.py +0 -0
  83. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/secrets/config.py +0 -0
  84. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/secrets/config_test.py +0 -0
  85. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/secrets/null.py +0 -0
  86. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/secrets/sops.py +0 -0
  87. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/secrets/sops_test.py +0 -0
  88. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/templating_test.py +0 -0
  89. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/fs.py +0 -0
  90. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/fs_test.py +0 -0
  91. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/kubectl.py +0 -0
  92. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/kubernetes.py +0 -0
  93. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/kvstore.py +0 -0
  94. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/loads.py +0 -0
  95. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/logging.py +0 -0
  96. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/shell.py +0 -0
  97. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/testing.py +0 -0
  98. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/typer.py +0 -0
  99. {nyl-0.1.1 → nyl-0.2.0}/src/nyl/tools/types.py +0 -0
  100. {nyl-0.1.1 → nyl-0.2.0}/tests/__init__.py +0 -0
  101. {nyl-0.1.1 → nyl-0.2.0}/tests/conftest.py +0 -0
  102. {nyl-0.1.1 → nyl-0.2.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nyl
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Advanced templating engine for Kubernetes and GitOps.
5
5
  Author-email: Niklas Rosenstein <rosensteinniklas@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nyl"
7
- version = "0.1.1" # sed'ed on publish in CI
7
+ version = "0.2.0" # sed'ed on publish in CI
8
8
  description = "Advanced templating engine for Kubernetes and GitOps."
9
9
  authors = [
10
10
  {name = "Niklas Rosenstein", email = "rosensteinniklas@gmail.com"},
@@ -29,12 +29,14 @@ from . import profile # noqa: E402
29
29
  from . import run # noqa: F401, E402
30
30
  from . import secrets # noqa: E402
31
31
  from . import template # noqa: F401,E402
32
+ from . import tools # noqa: E402
32
33
  from . import tun # noqa: E402
33
34
 
34
35
  app.add_typer(argocd.app)
35
36
  app.add_typer(new.app)
36
37
  app.add_typer(profile.app)
37
38
  app.add_typer(secrets.app)
39
+ app.add_typer(tools.app)
38
40
  app.add_typer(tun.app)
39
41
 
40
42
 
@@ -0,0 +1,61 @@
1
+ """
2
+ Interact with the secrets providers configured in `nyl-secrets.yaml`.
3
+ """
4
+
5
+ import json
6
+
7
+ from typer import Option
8
+ from nyl.secrets.config import SecretsConfig
9
+ from nyl.tools.typer import new_typer
10
+
11
+
12
+ app = new_typer(name="secrets", help=__doc__)
13
+
14
+ # Initialized from callback for access by subcommands.
15
+ provider: str
16
+
17
+
18
+ @app.callback()
19
+ def callback(
20
+ _provider: str = Option(
21
+ "default",
22
+ "--provider",
23
+ help="The name of the configured secrets provider to use.",
24
+ envvar="NYL_SECRETS",
25
+ ),
26
+ ) -> None:
27
+ """
28
+ Interact with the secrets providers configured in `nyl-secrets.yaml`.
29
+ """
30
+
31
+ global provider
32
+ provider = _provider
33
+
34
+
35
+ @app.command()
36
+ def list(
37
+ providers: bool = Option(
38
+ False, help="List the configured secrets providers instead of the current provider's available keys."
39
+ ),
40
+ ) -> None:
41
+ """
42
+ List the keys for all secrets in the provider.
43
+ """
44
+
45
+ secrets = SecretsConfig.load()
46
+ if providers:
47
+ for alias, impl in secrets.providers.items():
48
+ print(alias, impl)
49
+ else:
50
+ for key in secrets.providers[provider].keys():
51
+ print(key)
52
+
53
+
54
+ @app.command()
55
+ def get(key: str, pretty: bool = False) -> None:
56
+ """
57
+ Get the value of a secret as JSON.
58
+ """
59
+
60
+ secrets = SecretsConfig.load()
61
+ print(json.dumps(secrets.providers[provider].get(key), indent=4 if pretty else None))
@@ -66,6 +66,18 @@ def template(
66
66
  "This option must be disabled when passing the generated manifests to `kubectl apply --applyset=...`, as it "
67
67
  "would otherwise cause an error due to the label being present on the input data.",
68
68
  ),
69
+ default_namespace: Optional[str] = Option(
70
+ None,
71
+ help="The name of the Kubernetes namespace to fill in to Kubernetes resource that don't have a namespace set. "
72
+ "If this is not specified as an argument or via environment variables, it will default to the stem of the "
73
+ "manifest source filename. Note that if a manifest defines a Namespace resource, that namespace is used "
74
+ "instead, regardless of the value of this option. If a manifest source file defines multiple namespaces and a "
75
+ "resource without namespace is encountered, this option is considered if it matches one of the namespaces "
76
+ "defined in the file. Note that this option is usually problematic when rendering multiple files, as often "
77
+ "a single file is intended to deploy into a single namespace.\n\n"
78
+ "Note that Nyl's detection for cluster-scoped vs. namespace-scoped resources is not very good, yet.",
79
+ envvar="ARGOCD_APP_NAMESPACE",
80
+ ),
69
81
  inline: bool = Option(True, help="Evaluate Nyl inlined resources."),
70
82
  state_dir: Optional[Path] = Option(
71
83
  None, help="The directory to store state in (such as kubeconfig files).", envvar="NYL_STATE_DIR"
@@ -168,10 +180,10 @@ def template(
168
180
  )
169
181
 
170
182
  # Find the namespaces that are defined in the file. If we find any manifests without a namespace, we will
171
- # inject that namespace name into them.
172
- # Also find the applyset defined in the file.
183
+ # inject that namespace name into them. Also find the applyset defined in the file.
173
184
  namespaces: set[str] = set()
174
185
  applyset: ApplySet | None = None
186
+
175
187
  for manifest in list(source.manifests):
176
188
  if is_namespace_resource(manifest):
177
189
  namespaces.add(manifest["metadata"]["name"])
@@ -185,23 +197,40 @@ def template(
185
197
  applyset = ApplySet.load(manifest)
186
198
  source.manifests.remove(manifest)
187
199
 
200
+ # Determine what namespace to fill in if we encounter resources without it.
201
+ current_default_namespace = default_namespace
202
+ if current_default_namespace is None:
203
+ if len(namespaces) == 1:
204
+ current_default_namespace = next(iter(namespaces))
205
+ elif len(namespaces) == 0:
206
+ current_default_namespace = source.file.stem
207
+ elif len(namespaces) > 2 and current_default_namespace not in namespaces:
208
+ logger.error(
209
+ "The specified --default-namespace='{}' is not defined in manifest source file '{}', despite it "
210
+ "defining {} namespaces. If your manifest source file defines multiple namespaces, the specified "
211
+ "--default-namespace must be one of the namespaces defined in it (the file current defines "
212
+ "the following namespaces: {})",
213
+ default_namespace,
214
+ source.file,
215
+ len(namespaces),
216
+ ", ".join(namespaces),
217
+ )
218
+ exit(1)
219
+
188
220
  if not applyset and project.config.settings.generate_applysets:
189
- if len(namespaces) > 1:
221
+ if not current_default_namespace:
190
222
  logger.opt(ansi=True).error(
191
- "Multiple namespaces defined in <yellow>{}</>, but automatic ApplySet generation is enabled. "
192
- "There can only be one namespace per source in this case.",
223
+ "No default namespace defined for <yellow>{}</>, but it is required for the automatically "
224
+ "generated nyl.io/v1/ApplySet resource (the ApplySet is named after the default namespace).",
193
225
  source.file,
194
226
  )
195
227
  exit(1)
196
- elif len(namespaces) == 1:
197
- applyset_name = next(iter(namespaces))
198
- else:
199
- applyset_name = source.file.stem
200
228
 
229
+ applyset_name = current_default_namespace
230
+ applyset = ApplySet.new(applyset_name)
201
231
  logger.opt(ansi=True).info(
202
232
  "Automatically creating ApplySet for <blue>{}</> (name: <magenta>{}</>).", source.file, applyset_name
203
233
  )
204
- applyset = ApplySet.new(applyset_name)
205
234
 
206
235
  if applyset is not None:
207
236
  applyset.set_group_kinds(source.manifests)
@@ -236,7 +265,7 @@ def template(
236
265
 
237
266
  if "metadata" not in manifest:
238
267
  logger.opt(ansi=True).error(
239
- "Manifest in <yellow>'{}'</> has no <cyan>metadata</> key:\n\n{}",
268
+ "A manifest in <yellow>'{}'</> has no <cyan>metadata</> key:\n\n{}",
240
269
  source.file,
241
270
  indent(yaml.safe_dump(manifest), " "),
242
271
  )
@@ -247,30 +276,23 @@ def template(
247
276
  labels[APPLYSET_LABEL_PART_OF] = applyset.id
248
277
 
249
278
  if not is_cluster_scoped_resource(manifest) and "namespace" not in manifest["metadata"]:
250
- if len(namespaces) > 1:
279
+ if not current_default_namespace:
251
280
  logger.error(
252
- "Multiple namespaces defined in '{}', but manifest {}/{} does not have a namespace.",
281
+ "A namespace-scoped manifest in '{}' has no namespace, and no default namespace is defined. "
282
+ "Try defining a single namespace in the manifest source file, naming the manifest source file "
283
+ "the same as that default namespace or passing the --default-namespace option.",
253
284
  source.file,
254
- manifest["kind"],
255
- manifest["metadata"]["name"],
256
285
  )
257
286
  exit(1)
258
- elif len(namespaces) == 0:
259
- logger.warning(
260
- "No namespaces defined in '{}', injecting 'default' into manifest {}/{}.",
261
- source.file,
262
- manifest["kind"],
263
- manifest["metadata"]["name"],
264
- )
265
- manifest["metadata"]["namespace"] = "default"
266
- else:
267
- logger.trace(
268
- "Injecting namespace '{}' into manifest {}/{}.",
269
- next(iter(namespaces)),
270
- manifest["kind"],
271
- manifest["metadata"]["name"],
272
- )
273
- manifest["metadata"]["namespace"] = next(iter(namespaces))
287
+
288
+ logger.trace(
289
+ "Injecting namespace '{}' into manifest {}/{}.",
290
+ current_default_namespace,
291
+ manifest["kind"],
292
+ manifest["metadata"]["name"],
293
+ )
294
+ manifest["metadata"]["namespace"] = current_default_namespace
295
+
274
296
  elif not is_cluster_scoped_resource(manifest) and manifest["metadata"]["namespace"] not in namespaces:
275
297
  logger.warning(
276
298
  "Resource '{}/{}' in '{}' references namespace '{}', which is not defined in the file.",
@@ -0,0 +1,13 @@
1
+ """
2
+ Swiss-army-knife for various common operations in conjunction with Nyl (Kubernetes, secrets management, etc).
3
+ """
4
+
5
+ from nyl.tools.typer import new_typer
6
+
7
+
8
+ app = new_typer(name="tools", help=__doc__)
9
+
10
+ from . import sops # noqa: E402
11
+ from . import bcrypt # noqa: F401,E402
12
+
13
+ app.add_typer(sops.app)
@@ -0,0 +1,17 @@
1
+ import sys
2
+ from typing import Optional
3
+
4
+ from bcrypt import gensalt, hashpw
5
+ from . import app
6
+
7
+
8
+ @app.command()
9
+ def bcrypt(text: Optional[str] = None) -> None:
10
+ """
11
+ Bcrypt-hash the specified text. If no argument is specified, read from stdin.
12
+ """
13
+
14
+ if text is None:
15
+ text = sys.stdin.read()
16
+
17
+ print(hashpw(text.encode("utf-8"), gensalt()).decode("utf-8"))
@@ -1,8 +1,7 @@
1
1
  """
2
- Interact with the secrets providers configured in `nyl-secrets.yaml`.
2
+ Utilities for SOPS files.
3
3
  """
4
4
 
5
- import json
6
5
  from pathlib import Path
7
6
  from shlex import quote
8
7
  import sys
@@ -17,60 +16,17 @@ from nyl.tools.logging import lazy_str
17
16
  from nyl.tools.typer import new_typer
18
17
 
19
18
 
20
- app = new_typer(name="secrets", help=__doc__)
21
-
22
- sops = new_typer(name="sops", help=__doc__)
23
- app.add_typer(sops)
24
-
25
- # Initialized from callback for access by subcommands.
26
- provider: str
27
-
28
-
29
- @app.callback()
30
- def callback(
31
- _provider: str = Option(
32
- "default", "--provider", help="The name of the configured secrets provider to use.", envvar="NYL_SECRETS"
33
- ),
34
- ) -> None:
35
- """
36
- Interact with the secrets providers configured in `nyl-secrets.yaml`.
37
- """
38
-
39
- global provider
40
- provider = _provider
41
-
42
-
43
- @app.command()
44
- def list(
45
- providers: bool = Option(
46
- False, help="List the configured secrets providers instead of the current provider's available keys."
47
- ),
48
- ) -> None:
49
- """
50
- List the keys for all secrets in the provider.
51
- """
52
-
53
- secrets = SecretsConfig.load()
54
- if providers:
55
- for alias, impl in secrets.providers.items():
56
- print(alias, impl)
57
- else:
58
- for key in secrets.providers[provider].keys():
59
- print(key)
19
+ app = new_typer(name="sops", help=__doc__)
60
20
 
61
21
 
62
22
  @app.command()
63
- def get(key: str, pretty: bool = False) -> None:
64
- """
65
- Get the value of a secret as JSON.
66
- """
67
-
68
- secrets = SecretsConfig.load()
69
- print(json.dumps(secrets.providers[provider].get(key), indent=4 if pretty else None))
70
-
71
-
72
- @sops.command()
73
23
  def re_encrypt(
24
+ provider: str = Option(
25
+ "default",
26
+ "--provider",
27
+ help="The name of the configured secrets provider to use.",
28
+ envvar="NYL_SECRETS",
29
+ ),
74
30
  file: Optional[Path] = Option(
75
31
  None,
76
32
  help="The SOPS-file to re-encrypt. Defaults to the SOPS file mentioned in `nyl-secrets.yaml` "
@@ -110,7 +66,7 @@ def re_encrypt(
110
66
  sops.save(file_type)
111
67
 
112
68
 
113
- @sops.command()
69
+ @app.command()
114
70
  def export_dotenv(
115
71
  file: Path,
116
72
  prefix: str = Option("", help="Only export keys with the given prefix, and strip the prefix."),
@@ -51,6 +51,16 @@ class Placeholder(NylResource, api_version=API_VERSION_K8S):
51
51
  "properties": {
52
52
  "spec": {
53
53
  "type": "object",
54
+ "properties": {
55
+ "message": {
56
+ "type": "string",
57
+ "description": "A message that describes the missing resource.",
58
+ },
59
+ "reason": {
60
+ "type": "string",
61
+ "description": "The reason why the resource is missing (e.g. 'NotFound').",
62
+ },
63
+ },
54
64
  },
55
65
  },
56
66
  }
@@ -93,6 +93,12 @@ def lookup(api_version: str, kind: str, name: str, namespace: str) -> Any:
93
93
  return obj
94
94
 
95
95
 
96
+ class LookupError(Exception):
97
+ """
98
+ Raised when a `lookup()` call fails because the resource was not found.
99
+ """
100
+
101
+
96
102
  @dataclass
97
103
  class NylTemplateEngine:
98
104
  """
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
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
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