nyl 0.3.0__tar.gz → 0.5.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 (114) hide show
  1. {nyl-0.3.0 → nyl-0.5.0}/.kraken.py +2 -0
  2. {nyl-0.3.0 → nyl-0.5.0}/PKG-INFO +1 -1
  3. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration/profiles.md +21 -4
  4. nyl-0.5.0/docs/content/reference/configuration/secrets.md +152 -0
  5. {nyl-0.3.0 → nyl-0.5.0}/pyproject.toml +1 -1
  6. {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/client.pyi +1 -0
  7. {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/discovery.pyi +1 -0
  8. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/__init__.py +55 -25
  9. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/argocd.py +6 -4
  10. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/crds.py +1 -0
  11. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/new.py +5 -3
  12. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/profile.py +6 -5
  13. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/run.py +6 -4
  14. nyl-0.5.0/src/nyl/commands/secrets.py +93 -0
  15. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/template.py +65 -29
  16. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/tools/__init__.py +4 -3
  17. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/tools/bcrypt.py +1 -0
  18. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/tools/sops.py +6 -5
  19. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/tun.py +7 -6
  20. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/components.py +3 -1
  21. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/dispatch.py +3 -2
  22. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/dispatch_test.py +2 -1
  23. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/helmchart.py +4 -3
  24. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/statefulsecret.py +2 -1
  25. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/__init__.py +7 -8
  26. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/config.py +17 -1
  27. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/kubeconfig.py +16 -5
  28. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/tunnel.py +4 -3
  29. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/project/config.py +35 -10
  30. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/__init__.py +3 -1
  31. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/applyset.py +3 -2
  32. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/placeholder.py +3 -1
  33. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/postprocessor_test.py +1 -0
  34. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/statefulsecret.py +1 -0
  35. nyl-0.5.0/src/nyl/secrets/__init__.py +77 -0
  36. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/secrets/config.py +23 -45
  37. nyl-0.5.0/src/nyl/secrets/kubernetes.py +84 -0
  38. nyl-0.5.0/src/nyl/secrets/null.py +22 -0
  39. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/secrets/sops.py +39 -6
  40. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/secrets/sops_test.py +5 -2
  41. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/templating.py +31 -25
  42. nyl-0.5.0/src/nyl/tools/di.py +84 -0
  43. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/fs_test.py +1 -0
  44. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/kubernetes.py +2 -1
  45. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/kvstore.py +3 -3
  46. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/logging.py +0 -1
  47. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/typer.py +1 -0
  48. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/yaml.py +2 -1
  49. {nyl-0.3.0 → nyl-0.5.0}/tests/conftest.py +1 -0
  50. {nyl-0.3.0 → nyl-0.5.0}/uv.lock +137 -92
  51. nyl-0.3.0/docs/content/reference/configuration/secrets.md +0 -87
  52. nyl-0.3.0/src/nyl/commands/secrets.py +0 -61
  53. nyl-0.3.0/src/nyl/secrets/__init__.py +0 -1
  54. nyl-0.3.0/src/nyl/secrets/null.py +0 -14
  55. {nyl-0.3.0 → nyl-0.5.0}/.envrc +0 -0
  56. {nyl-0.3.0 → nyl-0.5.0}/.github/workflows/docker.yaml +0 -0
  57. {nyl-0.3.0 → nyl-0.5.0}/.github/workflows/docs.yaml +0 -0
  58. {nyl-0.3.0 → nyl-0.5.0}/.github/workflows/python.yaml +0 -0
  59. {nyl-0.3.0 → nyl-0.5.0}/.gitignore +0 -0
  60. {nyl-0.3.0 → nyl-0.5.0}/README.md +0 -0
  61. {nyl-0.3.0 → nyl-0.5.0}/argocd-cmp/Dockerfile +0 -0
  62. {nyl-0.3.0 → nyl-0.5.0}/argocd-cmp/README.md +0 -0
  63. {nyl-0.3.0 → nyl-0.5.0}/argocd-cmp/plugin.yaml +0 -0
  64. {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/.envrc +0 -0
  65. {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/.secrets.yaml +0 -0
  66. {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/.sops.yaml +0 -0
  67. {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/README.md +0 -0
  68. {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/argocd.yaml +0 -0
  69. {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/nyl-project.yaml +0 -0
  70. {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/nyl-secrets.yaml +0 -0
  71. {nyl-0.3.0 → nyl-0.5.0}/docs/content/index.md +0 -0
  72. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/applysets.md +0 -0
  73. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/argocd-plugin.md +0 -0
  74. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/cluster-connectivity.md +0 -0
  75. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration/environment.md +0 -0
  76. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration/projects.md +0 -0
  77. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration-api/profiles.md +0 -0
  78. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration-api/secrets.md +0 -0
  79. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/glossary.md +0 -0
  80. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/basics.md +0 -0
  81. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/components.md +0 -0
  82. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/inlining/helmcharts.md +0 -0
  83. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/inlining/overview.md +0 -0
  84. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/inlining/postprocessor.md +0 -0
  85. {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/secrets.md +0 -0
  86. {nyl-0.3.0 → nyl-0.5.0}/docs/mkdocs.yml +0 -0
  87. {nyl-0.3.0 → nyl-0.5.0}/docs/pyproject.toml +0 -0
  88. {nyl-0.3.0 → nyl-0.5.0}/docs/uv.lock +0 -0
  89. {nyl-0.3.0 → nyl-0.5.0}/example/manifests/postgres.yaml +0 -0
  90. {nyl-0.3.0 → nyl-0.5.0}/example/nyl-profiles.yaml +0 -0
  91. {nyl-0.3.0 → nyl-0.5.0}/example/nyl-secrets.yaml +0 -0
  92. {nyl-0.3.0 → nyl-0.5.0}/example/secrets.yaml +0 -0
  93. {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/README.md +0 -0
  94. {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/__init__.pyi +0 -0
  95. {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/__init__.pyi +0 -0
  96. {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/exceptions.pyi +0 -0
  97. {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/resource.pyi +0 -0
  98. {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/watch/__init__.pyi +0 -0
  99. {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/watch/watch.pyi +0 -0
  100. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/__init__.py +0 -0
  101. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/__init__.py +1 -1
  102. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/config_test.py +0 -0
  103. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/applyset_test.py +0 -0
  104. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/helmchart.py +0 -0
  105. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/postprocessor.py +3 -3
  106. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/secrets/config_test.py +0 -0
  107. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/templating_test.py +0 -0
  108. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/fs.py +2 -2
  109. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/kubectl.py +3 -3
  110. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/loads.py +0 -0
  111. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/shell.py +1 -1
  112. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/testing.py +0 -0
  113. {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/types.py +0 -0
  114. {nyl-0.3.0 → nyl-0.5.0}/tests/__init__.py +0 -0
@@ -1,5 +1,7 @@
1
1
  from kraken.common import buildscript
2
+
2
3
  buildscript(requirements=["kraken-build @ git+https://github.com/kraken-build/kraken.git@nr/python-project#egg=kraken-build&subdirectory=kraken-build"])
3
4
 
4
5
  from kraken.build.python.v1alpha1 import python_project
6
+
5
7
  python_project()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nyl
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Advanced templating engine for Kubernetes and GitOps.
5
5
  Author-email: Niklas Rosenstein <rosensteinniklas@gmail.com>
6
6
  License: MIT
@@ -45,9 +45,12 @@ machine and use that to connect to the Kubernetes cluster.
45
45
  ```toml title="nyl-profile.toml"
46
46
  [default.kubeconfig]
47
47
  type = "ssh"
48
+ sudo = false # default, set to true to use `sudo cat <path>`
48
49
  user = "root"
49
50
  host = "mycluster.example.com"
51
+ port = 22 # default
50
52
  path = "/etc/rancher/k3s/k3s.yaml"
53
+ # replace_apiserver_hostname
51
54
 
52
55
  [default.tunnel]
53
56
  type = "ssh"
@@ -61,9 +64,12 @@ machine and use that to connect to the Kubernetes cluster.
61
64
  default:
62
65
  kubeconfig:
63
66
  type: ssh
67
+ sudo: false
64
68
  user: root
65
69
  host: mycluster.example.com
70
+ port: 22
66
71
  path: /etc/rancher/k3s/k3s.yaml
72
+ replace_apiserver_hostname: null
67
73
  tunnel:
68
74
  type: ssh
69
75
  user: root
@@ -77,9 +83,12 @@ machine and use that to connect to the Kubernetes cluster.
77
83
  "default": {
78
84
  "kubeconfig": {
79
85
  "type": "ssh",
86
+ "sudo": false,
80
87
  "user": "root",
81
88
  "host": "mycluster.example.com",
82
- "path": "/etc/rancher/k3s/k3s.yaml"
89
+ "port": 22,
90
+ "path": "/etc/rancher/k3s/k3s.yaml",
91
+ "replace_apiserver_hostname": null
83
92
  },
84
93
  "tunnel": {
85
94
  "type": "ssh",
@@ -100,7 +109,7 @@ is reachable from your local machine.
100
109
  user = "root"
101
110
  host = "mycluster.example.com"
102
111
  path = "/etc/rancher/k3s/k3s.yaml"
103
- replace_apiserver_hostname = "mycluster.example.com"
112
+ replace_apiserver_hostname = "reachable-address.com"
104
113
  ```
105
114
 
106
115
  === "YAML"
@@ -112,7 +121,7 @@ is reachable from your local machine.
112
121
  user: root
113
122
  host: mycluster.example.com
114
123
  path: /etc/rancher/k3s/k3s.yaml
115
- replace_apiserver_hostname: mycluster.example.com
124
+ replace_apiserver_hostname: reachable-address.com
116
125
  ```
117
126
 
118
127
  === "JSON"
@@ -125,11 +134,19 @@ is reachable from your local machine.
125
134
  "user": "root",
126
135
  "host": "mycluster.example.com",
127
136
  "path": "/etc/rancher/k3s/k3s.yaml",
128
- "replace_apiserver_hostname": "mycluster.example.com"
137
+ "replace_apiserver_hostname": "reachable-address.com"
129
138
  }
130
139
  }
131
140
  ```
132
141
 
142
+ !!! note
143
+
144
+ When retrieving a Kubeconfig via SSH that has `127.0.0.1`, `0.0.0.0` or `localhost` as the hostname in the `server`
145
+ field, and `replace_apiserver_hostname` is not specified, Nyl will automatically replace it with the `host` field
146
+ specified that was used to execute the remote command to read the Kubeconfig. This is because we expect the two
147
+ hosts to be the same, and you cannot reach the Kubernetes API server through your local machine without using tunnel
148
+ (which you can use, see [Tunnel management](#tunnel-management)).
149
+
133
150
  If you are you are already setup with a kubeconfig file, you can specify the path to the file directly or have it
134
151
  automatically use your `~/.kube/config` file/`KUBECONFIG` environment variable. You may specify the context to use
135
152
  from that kubeconfig file which ensures Nyl interacts with the correct cluster, even if your
@@ -0,0 +1,152 @@
1
+ # Secrets
2
+
3
+ You can connect Nyl with various secret providers to retrieve external or encrypted data that can be used in your
4
+ templates. This is useful for keeping sensitive data out of your configuration files and ensuring that they are not
5
+ accidentally committed to a version control system.
6
+
7
+ ## Configuration
8
+
9
+ Secret providers are configured in a `nyl-secrets.<ext>` file that is located in the current working directory or any of
10
+ its parent directories. Secret providers may also be defined in a [Project configuration file](./projects.md), though
11
+ the file closer to the working directory will take precedence.
12
+
13
+ There is no "global" way to define a secrets provider, as secrets are considered project-specific.
14
+
15
+ As with other configuration file types, the file extension can be `.toml`, `.yaml`, or `.json`.
16
+
17
+ The configuration contains any number of named secret providers. When not specified otherwise, Nyl will assume that the
18
+ provider to use is named `default`. The provider to use can be overriden by passing the corresponding CLI option to
19
+ respective Nyl commands or by setting the `NYL_SECRETS` environment variable.
20
+
21
+ ## Inspecting secret providers
22
+
23
+ You can inspect secret providers using the `nyl secrets` command.
24
+
25
+ ```
26
+ nyl secrets list List the keys for all secrets in the provider.
27
+ nyl secrets get <key> Get the value of a secret as JSON.
28
+ ```
29
+
30
+ ## Templating
31
+
32
+ Secrets are made available to templates using the `secrets.get()` function. The function takes a single argument, the
33
+ key of the secret to retrieve.
34
+
35
+ __Example__
36
+
37
+ ```yaml
38
+ apiVersion: v1
39
+ kind: Secret
40
+ metadata:
41
+ name: my-secret
42
+ type: Opaque
43
+ stringData:
44
+ password: ${{ secrets.get("my-password") }}
45
+ ```
46
+
47
+ ## CLI
48
+
49
+ The `nyl secrets` command allows you to interact with the configured secret provider(s).
50
+
51
+ ```
52
+ Usage: nyl secrets [OPTIONS] COMMAND [ARGS]...
53
+
54
+ Interact with the secrets providers configured in `nyl-secrets.yaml`.
55
+
56
+ ╭─ Options ────────────────────────────────────────────────────────────────────╮
57
+ │ --provider TEXT The name of the configured secrets provider to use. │
58
+ │ [env var: NYL_SECRETS] │
59
+ │ [default: default] │
60
+ │ --help Show this message and exit. │
61
+ ╰──────────────────────────────────────────────────────────────────────────────╯
62
+ ╭─ Commands ───────────────────────────────────────────────────────────────────╮
63
+ │ get Get the value of a secret as JSON. │
64
+ │ list List the keys for all secrets in the provider. │
65
+ │ set Set the value of a secret. │
66
+ │ unset Unset the value of a secret. │
67
+ ╰──────────────────────────────────────────────────────────────────────────────╯
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Provider: [Sops]
73
+
74
+ [Sops]: https://github.com/getsops/sops
75
+
76
+ Allows you to retrieve secrets from a [Sops] encrypted file. For a GitOps workflow, the file must be commited to the
77
+ same repository to ensure that Nyl has access to it when it is invoked as an ArgoCD Config Management plugin. You also
78
+ must have the `sops` program installed.
79
+
80
+ __Example__
81
+
82
+ === "TOML"
83
+
84
+ ```toml title="nyl-secrets.toml"
85
+ [default]
86
+ type = "sops"
87
+ path = "../secrets.yaml"
88
+ ```
89
+
90
+ === "YAML"
91
+
92
+ ```yaml title="nyl-secrets.yaml"
93
+ default:
94
+ type: sops
95
+ path: ../secrets.yaml
96
+ ```
97
+
98
+ === "JSON"
99
+
100
+ ```toml title="nyl-secrets.json"
101
+ {
102
+ "default": {
103
+ "type": "sops",
104
+ "path": "../secrets.yaml"
105
+ }
106
+ }
107
+ ```
108
+
109
+ The secrets will be decoded using the `sops` program, hence all the typical ways to configure Sops and how it decrypts
110
+ files apply. The `path` field is relative to the location of the `nyl-secrets.yaml` file.
111
+
112
+ ---
113
+
114
+ ## Provider: [KubernetesSecret](https://kubernetes.io/docs/concepts/configuration/secret/)
115
+
116
+ Allows you to point to a Kubernetes Secret as a source for secrets. Since Kubernetes secret values must be strings,
117
+ this provider does not currently support nested keys, although it could be made possible by assuming TOML/YAML/JSON
118
+ format for the Kubernetes `Secret` keys in the `data` field.
119
+
120
+ The secret provider will use the same Kubernetes context that is configured in Nyl via [Profiles](./profiles.md).
121
+
122
+ __Example__
123
+
124
+ === "TOML"
125
+
126
+ ```toml title="nyl-secrets.toml"
127
+ [default]
128
+ type = "KubernetesSecret"
129
+ name = "nyl-secrets"
130
+ namespace = "default"
131
+ ```
132
+
133
+ === "YAML"
134
+
135
+ ```yaml title="nyl-secrets.yaml"
136
+ default:
137
+ type: KubernetesSecret
138
+ name: nyl-secrets
139
+ namespace: default
140
+ ```
141
+
142
+ === "JSON"
143
+
144
+ ```toml title="nyl-secrets.json"
145
+ {
146
+ "default": {
147
+ "type": "KubernetesSecret",
148
+ "name": "nyl-secrets",
149
+ "namespace": "default"
150
+ }
151
+ }
152
+ ```
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nyl"
7
- version = "0.3.0" # sed'ed on publish in CI
7
+ version = "0.5.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"},
@@ -1,4 +1,5 @@
1
1
  from typing import Any, Iterable, Protocol
2
+
2
3
  from kubernetes.client.api_client import ApiClient
3
4
  from kubernetes.dynamic.discovery import Discoverer
4
5
  from kubernetes.dynamic.resource import Resource, ResourceInstance
@@ -1,5 +1,6 @@
1
1
  from collections.abc import Sequence
2
2
  from typing import Any
3
+
3
4
  from kubernetes.dynamic.resource import Resource
4
5
 
5
6
  class Discoverer:
@@ -6,39 +6,30 @@ applications directly or integrate as an ArgoCD ConfigManagementPlugin.
6
6
  import atexit
7
7
  import json
8
8
  import os
9
- from pathlib import Path
9
+ import sys
10
10
  import time
11
- from typing import Optional
12
- from nyl import __version__
11
+ from dataclasses import dataclass
13
12
  from enum import Enum
14
- import sys
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
15
16
  from loguru import logger
16
- from typer import Option
17
+ from typer import Option, Typer
18
+
19
+ from kubernetes.client.api_client import ApiClient
20
+ from nyl import __version__
21
+ from nyl.profiles import ProfileManager
22
+ from nyl.project.config import ProjectConfig
23
+ from nyl.secrets.config import SecretsConfig
24
+ from nyl.tools.di import DependenciesProvider
17
25
  from nyl.tools.logging import lazy_str
18
26
  from nyl.tools.shell import pretty_cmd
19
27
  from nyl.tools.typer import new_typer
20
28
 
29
+ app: Typer = new_typer(help=__doc__)
21
30
 
22
- app = new_typer(help=__doc__)
23
-
24
-
25
- from . import argocd # noqa: E402
26
- from . import crds # noqa: F401,E402
27
- from . import new # noqa: E402
28
- from . import profile # noqa: E402
29
- from . import run # noqa: F401, E402
30
- from . import secrets # noqa: E402
31
- from . import template # noqa: F401,E402
32
- from . import tools # noqa: E402
33
- from . import tun # noqa: E402
34
-
35
- app.add_typer(argocd.app)
36
- app.add_typer(new.app)
37
- app.add_typer(profile.app)
38
- app.add_typer(secrets.app)
39
- app.add_typer(tools.app)
40
- app.add_typer(tun.app)
41
-
31
+ # A global instance that we use for dependency injection.
32
+ PROVIDER = DependenciesProvider.default()
42
33
 
43
34
  LOG_TIME_FORMAT = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>"
44
35
  LOG_LEVEL_FORAMT = "<level>{level: <8}</level>"
@@ -55,6 +46,15 @@ class LogLevel(str, Enum):
55
46
  CRITICAL = "critical"
56
47
 
57
48
 
49
+ # Retrieving the Kubernetes API client depends on whether in-cluster configuration should be used or not.
50
+ @dataclass(kw_only=True)
51
+ class ApiClientConfig:
52
+ in_cluster: bool
53
+ " Load the in-cluster configuration if enabled; forego any Nyl profile configuration. "
54
+ profile: str | None
55
+ " If not loading the in-cluster configuration, use the given Nyl profile. Otherwise, use the default kubeconfig. "
56
+
57
+
58
58
  @app.callback()
59
59
  def _callback(
60
60
  quiet: bool = Option(False, "--quiet", "-q", help="Shortcut for --log-level=error."),
@@ -98,6 +98,18 @@ def _callback(
98
98
  lazy_str(pretty_cmd, sys.argv),
99
99
  )
100
100
 
101
+ PROVIDER.set_lazy(ProfileManager, lambda: ProfileManager.load(required=False))
102
+ PROVIDER.set_lazy(SecretsConfig, lambda: SecretsConfig.load(dependencies=PROVIDER))
103
+ PROVIDER.set_lazy(ProjectConfig, lambda: ProjectConfig.load(dependencies=PROVIDER))
104
+ PROVIDER.set_lazy(
105
+ ApiClient,
106
+ lambda: template.get_incluster_kubernetes_client()
107
+ if PROVIDER.get(ApiClientConfig).in_cluster
108
+ else template.get_profile_kubernetes_client(
109
+ PROVIDER.get(ProfileManager), PROVIDER.get(ApiClientConfig).profile
110
+ ),
111
+ )
112
+
101
113
 
102
114
  @app.command()
103
115
  def version() -> None:
@@ -107,3 +119,21 @@ def version() -> None:
107
119
 
108
120
  print(f"Nyl v{__version__}")
109
121
  sys.exit(0)
122
+
123
+
124
+ from . import argocd # noqa: E402
125
+ from . import crds # noqa: F401,E402
126
+ from . import new # noqa: E402
127
+ from . import profile # noqa: E402
128
+ from . import run # noqa: F401, E402
129
+ from . import secrets # noqa: E402
130
+ from . import template # noqa: F401,E402
131
+ from . import tools # noqa: E402
132
+ from . import tun # noqa: E402
133
+
134
+ app.add_typer(argocd.app)
135
+ app.add_typer(new.app)
136
+ app.add_typer(profile.app)
137
+ app.add_typer(secrets.app)
138
+ app.add_typer(tools.app)
139
+ app.add_typer(tun.app)
@@ -5,12 +5,14 @@ Helper commands for the ArgoCD integration.
5
5
  import sys
6
6
 
7
7
  from loguru import logger
8
+ from typer import Typer
9
+
10
+ from nyl.commands import PROVIDER
8
11
  from nyl.project.config import ProjectConfig
9
12
  from nyl.secrets.config import SecretsConfig
10
13
  from nyl.tools.typer import new_typer
11
14
 
12
-
13
- app = new_typer(name="argocd", help=__doc__)
15
+ app: Typer = new_typer(name="argocd", help=__doc__)
14
16
 
15
17
 
16
18
  @app.command()
@@ -21,10 +23,10 @@ def discovery() -> None:
21
23
  """
22
24
 
23
25
  found = False
24
- if (file := ProjectConfig.load().file) is not None:
26
+ if (file := PROVIDER.get(ProjectConfig).file) is not None:
25
27
  print(file.absolute())
26
28
  found = True
27
- if (file := SecretsConfig.load().file) is not None:
29
+ if (file := PROVIDER.get(SecretsConfig).file) is not None:
28
30
  print(file.absolute())
29
31
  found = True
30
32
 
@@ -2,6 +2,7 @@ import yaml
2
2
 
3
3
  from nyl.resources.applyset import ApplySet
4
4
  from nyl.resources.placeholder import Placeholder
5
+
5
6
  from . import app
6
7
 
7
8
 
@@ -7,11 +7,13 @@ from pathlib import Path
7
7
  from textwrap import dedent
8
8
 
9
9
  from loguru import logger
10
- from typer import Option
10
+ from typer import Option, Typer
11
+
12
+ from nyl.commands import PROVIDER
11
13
  from nyl.project.config import ProjectConfig
12
14
  from nyl.tools.typer import new_typer
13
15
 
14
- app = new_typer(name="new", help=__doc__)
16
+ app: Typer = new_typer(name="new", help=__doc__)
15
17
 
16
18
 
17
19
  class ComponentType(Enum):
@@ -106,5 +108,5 @@ def component(
106
108
  ) -> None:
107
109
  """Create the boilerplate for a Nyl component in the components directory."""
108
110
 
109
- components_path = ProjectConfig.load().get_components_path()
111
+ components_path = PROVIDER.get(ProjectConfig).get_components_path()
110
112
  chart(components_path / api_version / kind)
@@ -4,12 +4,13 @@ Interact with your Nyl profile configuration.
4
4
 
5
5
  import shlex
6
6
 
7
- from typer import Argument
7
+ from typer import Argument, Typer
8
+
9
+ from nyl.commands import PROVIDER
8
10
  from nyl.profiles import ProfileManager
9
11
  from nyl.tools.typer import new_typer
10
12
 
11
-
12
- app = new_typer(name="profile", help=__doc__)
13
+ app: Typer = new_typer(name="profile", help=__doc__)
13
14
 
14
15
 
15
16
  @app.command()
@@ -20,7 +21,7 @@ def activate(profile_name: str = Argument("default", envvar="NYL_PROFILE")) -> N
20
21
  Evaluate the stdout of this command to export the KUBECONFIG into your environment.
21
22
  """
22
23
 
23
- with ProfileManager.load() as manager:
24
+ with PROVIDER.get(ProfileManager) as manager:
24
25
  profile = manager.activate_profile(profile_name)
25
26
 
26
27
  for key, value in profile.env.items():
@@ -33,7 +34,7 @@ def get_kubeconfig(profile_name: str = Argument("default", envvar="NYL_PROFILE")
33
34
  Similar to `nyl profile activate`, but prints only the path to the `KUBECONFIG` file.
34
35
  """
35
36
 
36
- with ProfileManager.load() as manager:
37
+ with PROVIDER.get(ProfileManager) as manager:
37
38
  profile = manager.activate_profile(profile_name)
38
39
 
39
40
  print(profile.kubeconfig)
@@ -1,18 +1,20 @@
1
1
  import atexit
2
2
  import os
3
- from pathlib import Path
4
3
  import subprocess
5
4
  import sys
5
+ from pathlib import Path
6
6
  from tempfile import TemporaryDirectory
7
+
7
8
  from loguru import logger
8
9
  from typer import Argument, Option
9
- from nyl.tools import yaml
10
+
11
+ from nyl.commands import PROVIDER
10
12
  from nyl.profiles import ActivatedProfile, ProfileManager
11
13
  from nyl.profiles.kubeconfig import _trim_to_context
14
+ from nyl.tools import yaml
12
15
  from nyl.tools.logging import lazy_str
13
16
  from nyl.tools.shell import pretty_cmd
14
17
 
15
-
16
18
  from . import app
17
19
 
18
20
  # app = new_typer(name="run", help=__doc__)
@@ -42,7 +44,7 @@ def run(
42
44
  `nyl-profiles.yaml` configuration or from the same-named context in the global kubeconfig.
43
45
  """
44
46
 
45
- manager = ProfileManager.load(required=False)
47
+ manager = PROVIDER.get(ProfileManager)
46
48
  if manager and profile_name in manager.config.profiles:
47
49
  with manager:
48
50
  profile = manager.activate_profile(profile_name)
@@ -0,0 +1,93 @@
1
+ """
2
+ Interact with the secrets providers configured in `nyl-secrets.yaml`.
3
+ """
4
+
5
+ import json
6
+ import json as _json
7
+
8
+ from loguru import logger
9
+ from typer import Option, Typer
10
+
11
+ from nyl.commands import PROVIDER, ApiClientConfig
12
+ from nyl.secrets import SecretProvider
13
+ from nyl.secrets.config import SecretsConfig
14
+ from nyl.tools.typer import new_typer
15
+
16
+ app: Typer = new_typer(name="secrets", help=__doc__)
17
+
18
+
19
+ @app.callback()
20
+ def callback(
21
+ provider: str = Option(
22
+ "default",
23
+ "--provider",
24
+ help="The name of the configured secrets provider to use.",
25
+ envvar="NYL_SECRETS",
26
+ ),
27
+ profile: str | None = Option(
28
+ None,
29
+ "--profile",
30
+ help="The Nyl profile to assume.",
31
+ envvar="NYL_PROFILE",
32
+ ),
33
+ ) -> None:
34
+ """
35
+ Interact with the secrets providers configured in `nyl-secrets.yaml`.
36
+ """
37
+
38
+ PROVIDER.set(ApiClientConfig, ApiClientConfig(in_cluster=False, profile=profile))
39
+ PROVIDER.set_lazy(tuple[str, SecretProvider], lambda: (provider, PROVIDER.get(SecretsConfig).providers[provider]))
40
+ PROVIDER.set_lazy(SecretProvider, lambda: PROVIDER.get(tuple[str, SecretProvider])[1]) # type: ignore[type-abstract]
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
+ if providers:
54
+ for alias, impl in PROVIDER.get(SecretsConfig).providers.items():
55
+ print(alias, impl)
56
+ else:
57
+ for key in PROVIDER.get(SecretProvider).keys(): # type: ignore[type-abstract]
58
+ print(key)
59
+
60
+
61
+ @app.command()
62
+ def get(key: str, pretty: bool = False, raw: bool = False) -> None:
63
+ """
64
+ Get the value of a secret as JSON.
65
+ """
66
+
67
+ value = PROVIDER.get(SecretProvider).get(key) # type: ignore[type-abstract]
68
+ if raw and isinstance(value, str):
69
+ print(value)
70
+ else:
71
+ print(json.dumps(value, indent=4 if pretty else None))
72
+
73
+
74
+ @app.command()
75
+ def set(key: str, value: str, json: bool = False) -> None:
76
+ """
77
+ Set the value of a secret.
78
+ """
79
+
80
+ provider_name, secrets = PROVIDER.get(tuple[str, SecretProvider])
81
+ logger.info("Setting key '{}' in provider '{}'", key, provider_name)
82
+ secrets.set(key, _json.loads(value) if json else value)
83
+
84
+
85
+ @app.command()
86
+ def unset(key: str) -> None:
87
+ """
88
+ Unset the value of a secret.
89
+ """
90
+
91
+ provider_name, secrets = PROVIDER.get(tuple[str, SecretProvider])
92
+ logger.info("Unsetting key '{}' in provider '{}'", key, provider_name)
93
+ secrets.unset(key)