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.
- {nyl-0.3.0 → nyl-0.5.0}/.kraken.py +2 -0
- {nyl-0.3.0 → nyl-0.5.0}/PKG-INFO +1 -1
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration/profiles.md +21 -4
- nyl-0.5.0/docs/content/reference/configuration/secrets.md +152 -0
- {nyl-0.3.0 → nyl-0.5.0}/pyproject.toml +1 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/client.pyi +1 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/discovery.pyi +1 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/__init__.py +55 -25
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/argocd.py +6 -4
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/crds.py +1 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/new.py +5 -3
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/profile.py +6 -5
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/run.py +6 -4
- nyl-0.5.0/src/nyl/commands/secrets.py +93 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/template.py +65 -29
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/tools/__init__.py +4 -3
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/tools/bcrypt.py +1 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/tools/sops.py +6 -5
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/commands/tun.py +7 -6
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/components.py +3 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/dispatch.py +3 -2
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/dispatch_test.py +2 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/helmchart.py +4 -3
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/statefulsecret.py +2 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/__init__.py +7 -8
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/config.py +17 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/kubeconfig.py +16 -5
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/tunnel.py +4 -3
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/project/config.py +35 -10
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/__init__.py +3 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/applyset.py +3 -2
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/placeholder.py +3 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/postprocessor_test.py +1 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/statefulsecret.py +1 -0
- nyl-0.5.0/src/nyl/secrets/__init__.py +77 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/secrets/config.py +23 -45
- nyl-0.5.0/src/nyl/secrets/kubernetes.py +84 -0
- nyl-0.5.0/src/nyl/secrets/null.py +22 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/secrets/sops.py +39 -6
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/secrets/sops_test.py +5 -2
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/templating.py +31 -25
- nyl-0.5.0/src/nyl/tools/di.py +84 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/fs_test.py +1 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/kubernetes.py +2 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/kvstore.py +3 -3
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/logging.py +0 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/typer.py +1 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/yaml.py +2 -1
- {nyl-0.3.0 → nyl-0.5.0}/tests/conftest.py +1 -0
- {nyl-0.3.0 → nyl-0.5.0}/uv.lock +137 -92
- nyl-0.3.0/docs/content/reference/configuration/secrets.md +0 -87
- nyl-0.3.0/src/nyl/commands/secrets.py +0 -61
- nyl-0.3.0/src/nyl/secrets/__init__.py +0 -1
- nyl-0.3.0/src/nyl/secrets/null.py +0 -14
- {nyl-0.3.0 → nyl-0.5.0}/.envrc +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/.github/workflows/docker.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/.github/workflows/docs.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/.github/workflows/python.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/.gitignore +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/README.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-cmp/Dockerfile +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-cmp/README.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-cmp/plugin.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/.envrc +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/.secrets.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/.sops.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/README.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/argocd.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/nyl-project.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/argocd-with-nyl/nyl-secrets.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/index.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/applysets.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/argocd-plugin.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/cluster-connectivity.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration/environment.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration/projects.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration-api/profiles.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/configuration-api/secrets.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/glossary.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/basics.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/components.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/inlining/helmcharts.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/inlining/overview.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/inlining/postprocessor.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/content/reference/templating/secrets.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/mkdocs.yml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/pyproject.toml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/docs/uv.lock +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/example/manifests/postgres.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/example/nyl-profiles.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/example/nyl-secrets.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/example/secrets.yaml +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/README.md +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/__init__.pyi +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/__init__.pyi +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/exceptions.pyi +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/dynamic/resource.pyi +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/watch/__init__.pyi +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/kubernetes/watch/watch.pyi +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/__init__.py +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/generator/__init__.py +1 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/profiles/config_test.py +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/applyset_test.py +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/helmchart.py +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/resources/postprocessor.py +3 -3
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/secrets/config_test.py +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/templating_test.py +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/fs.py +2 -2
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/kubectl.py +3 -3
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/loads.py +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/shell.py +1 -1
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/testing.py +0 -0
- {nyl-0.3.0 → nyl-0.5.0}/src/nyl/tools/types.py +0 -0
- {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()
|
{nyl-0.3.0 → nyl-0.5.0}/PKG-INFO
RENAMED
|
@@ -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
|
-
"
|
|
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 = "
|
|
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:
|
|
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": "
|
|
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.
|
|
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"},
|
|
@@ -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
|
-
|
|
9
|
+
import sys
|
|
10
10
|
import time
|
|
11
|
-
from
|
|
12
|
-
from nyl import __version__
|
|
11
|
+
from dataclasses import dataclass
|
|
13
12
|
from enum import Enum
|
|
14
|
-
import
|
|
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
|
-
|
|
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 :=
|
|
26
|
+
if (file := PROVIDER.get(ProjectConfig).file) is not None:
|
|
25
27
|
print(file.absolute())
|
|
26
28
|
found = True
|
|
27
|
-
if (file :=
|
|
29
|
+
if (file := PROVIDER.get(SecretsConfig).file) is not None:
|
|
28
30
|
print(file.absolute())
|
|
29
31
|
found = True
|
|
30
32
|
|
|
@@ -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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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)
|