nyl 0.4.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.4.0 → nyl-0.5.0}/.kraken.py +2 -0
- {nyl-0.4.0 → nyl-0.5.0}/PKG-INFO +1 -1
- nyl-0.5.0/docs/content/reference/configuration/secrets.md +152 -0
- {nyl-0.4.0 → nyl-0.5.0}/pyproject.toml +1 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/kubernetes/dynamic/client.pyi +1 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/kubernetes/dynamic/discovery.pyi +1 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/commands/__init__.py +55 -25
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/commands/argocd.py +6 -4
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/commands/crds.py +1 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/commands/new.py +5 -3
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/commands/profile.py +6 -5
- {nyl-0.4.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.4.0 → nyl-0.5.0}/src/nyl/commands/template.py +65 -29
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/commands/tools/__init__.py +4 -3
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/commands/tools/bcrypt.py +1 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/commands/tools/sops.py +6 -5
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/commands/tun.py +7 -6
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/generator/components.py +3 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/generator/dispatch.py +3 -2
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/generator/dispatch_test.py +2 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/generator/helmchart.py +4 -3
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/generator/statefulsecret.py +2 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/profiles/__init__.py +7 -8
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/profiles/config.py +3 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/profiles/kubeconfig.py +5 -3
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/profiles/tunnel.py +4 -3
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/project/config.py +35 -10
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/resources/__init__.py +3 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/resources/applyset.py +3 -2
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/resources/placeholder.py +3 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/resources/postprocessor_test.py +1 -0
- {nyl-0.4.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.4.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.4.0 → nyl-0.5.0}/src/nyl/secrets/sops.py +39 -6
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/secrets/sops_test.py +5 -2
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/templating.py +31 -25
- nyl-0.5.0/src/nyl/tools/di.py +84 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/fs_test.py +1 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/kubernetes.py +2 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/kvstore.py +3 -3
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/logging.py +0 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/typer.py +1 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/yaml.py +2 -1
- {nyl-0.4.0 → nyl-0.5.0}/tests/conftest.py +1 -0
- {nyl-0.4.0 → nyl-0.5.0}/uv.lock +137 -92
- nyl-0.4.0/docs/content/reference/configuration/secrets.md +0 -87
- nyl-0.4.0/src/nyl/commands/secrets.py +0 -61
- nyl-0.4.0/src/nyl/secrets/__init__.py +0 -1
- nyl-0.4.0/src/nyl/secrets/null.py +0 -14
- {nyl-0.4.0 → nyl-0.5.0}/.envrc +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/.github/workflows/docker.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/.github/workflows/docs.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/.github/workflows/python.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/.gitignore +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/README.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-cmp/Dockerfile +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-cmp/README.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-cmp/plugin.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-with-nyl/.envrc +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-with-nyl/.secrets.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-with-nyl/.sops.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-with-nyl/README.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-with-nyl/argocd.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-with-nyl/nyl-project.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/argocd-with-nyl/nyl-secrets.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/index.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/applysets.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/argocd-plugin.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/cluster-connectivity.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/configuration/environment.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/configuration/profiles.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/configuration/projects.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/configuration-api/profiles.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/configuration-api/secrets.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/glossary.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/templating/basics.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/templating/components.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/templating/inlining/helmcharts.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/templating/inlining/overview.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/templating/inlining/postprocessor.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/content/reference/templating/secrets.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/mkdocs.yml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/pyproject.toml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/docs/uv.lock +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/example/manifests/postgres.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/example/nyl-profiles.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/example/nyl-secrets.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/example/secrets.yaml +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/kubernetes/README.md +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/kubernetes/__init__.pyi +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/kubernetes/dynamic/__init__.pyi +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/kubernetes/dynamic/exceptions.pyi +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/kubernetes/dynamic/resource.pyi +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/kubernetes/watch/__init__.pyi +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/kubernetes/watch/watch.pyi +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/__init__.py +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/generator/__init__.py +1 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/profiles/config_test.py +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/resources/applyset_test.py +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/resources/helmchart.py +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/resources/postprocessor.py +3 -3
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/secrets/config_test.py +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/templating_test.py +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/fs.py +2 -2
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/kubectl.py +3 -3
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/loads.py +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/shell.py +1 -1
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/testing.py +0 -0
- {nyl-0.4.0 → nyl-0.5.0}/src/nyl/tools/types.py +0 -0
- {nyl-0.4.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.4.0 → nyl-0.5.0}/PKG-INFO
RENAMED
|
@@ -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)
|
|
@@ -1,33 +1,45 @@
|
|
|
1
1
|
import atexit
|
|
2
|
-
from dataclasses import dataclass
|
|
3
2
|
import os
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from textwrap import indent
|
|
6
|
-
from typing import Optional
|
|
7
|
+
from typing import Any, Literal, Optional, cast
|
|
8
|
+
|
|
7
9
|
from loguru import logger
|
|
8
|
-
from nyl.resources.postprocessor import PostProcessor
|
|
9
10
|
from typer import Argument, Option
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
from kubernetes.client.api_client import ApiClient
|
|
13
|
+
from kubernetes.config.incluster_config import load_incluster_config
|
|
14
|
+
from kubernetes.config.kube_config import load_kube_config
|
|
15
|
+
from nyl.commands import PROVIDER, ApiClientConfig, app
|
|
11
16
|
from nyl.generator import reconcile_generator
|
|
12
17
|
from nyl.generator.dispatch import DispatchingGenerator
|
|
13
18
|
from nyl.profiles import ProfileManager
|
|
14
|
-
from kubernetes.config.incluster_config import load_incluster_config
|
|
15
|
-
from kubernetes.config.kube_config import load_kube_config
|
|
16
|
-
from kubernetes.client.api_client import ApiClient
|
|
17
19
|
from nyl.project.config import ProjectConfig
|
|
18
20
|
from nyl.resources import API_VERSION_INLINE, NylResource
|
|
19
21
|
from nyl.resources.applyset import APPLYSET_LABEL_PART_OF, ApplySet
|
|
22
|
+
from nyl.resources.postprocessor import PostProcessor
|
|
20
23
|
from nyl.secrets.config import SecretsConfig
|
|
21
24
|
from nyl.templating import NylTemplateEngine
|
|
25
|
+
from nyl.tools import yaml
|
|
22
26
|
from nyl.tools.kubectl import Kubectl
|
|
23
27
|
from nyl.tools.logging import lazy_str
|
|
24
28
|
from nyl.tools.types import Manifest, Manifests
|
|
25
29
|
|
|
26
|
-
from . import app
|
|
27
|
-
|
|
28
30
|
DEFAULT_PROFILE = "default"
|
|
29
31
|
|
|
30
32
|
|
|
33
|
+
# Need an enum for typer
|
|
34
|
+
class OnLookupFailure(str, Enum):
|
|
35
|
+
Error = "Error"
|
|
36
|
+
CreatePlaceholder = "CreatePlaceholder"
|
|
37
|
+
SkipResource = "SkipResource"
|
|
38
|
+
|
|
39
|
+
def to_literal(self) -> Literal["Error", "CreatePlaceholder", "SkipResource"]:
|
|
40
|
+
return cast(Any, self.name) # type: ignore[no-any-return]
|
|
41
|
+
|
|
42
|
+
|
|
31
43
|
@dataclass
|
|
32
44
|
class ManifestsWithSource:
|
|
33
45
|
"""
|
|
@@ -38,6 +50,37 @@ class ManifestsWithSource:
|
|
|
38
50
|
file: Path
|
|
39
51
|
|
|
40
52
|
|
|
53
|
+
def get_incluster_kubernetes_client() -> ApiClient:
|
|
54
|
+
logger.info("Using in-cluster configuration.")
|
|
55
|
+
load_incluster_config()
|
|
56
|
+
return ApiClient()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_profile_kubernetes_client(profiles: ProfileManager, profile: str | None) -> ApiClient:
|
|
60
|
+
"""
|
|
61
|
+
Create a Kubernetes :class:`ApiClient` from the selected *profile*.
|
|
62
|
+
|
|
63
|
+
If no *profile* is specified, but the profile manager contains at least one profile, the *profile* argument will
|
|
64
|
+
default to the value of :data:`DEFAULT_PROFILE` (which is `"default"`). Otherwise, if no profile is selected and
|
|
65
|
+
none is configured, the standard Kubernetes config load takes place (i.e. try `KUBECONFIG` and then
|
|
66
|
+
`~/.kube/config`).
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
with profiles:
|
|
70
|
+
# If no profile to activate is specified, and there are no profiles defined, we're not activating a
|
|
71
|
+
# a profile. It should be valid to use Nyl without a `nyl-profiles.yaml` file.
|
|
72
|
+
if profile is not None or profiles.config.profiles:
|
|
73
|
+
profile = profile or DEFAULT_PROFILE
|
|
74
|
+
active = profiles.activate_profile(profile)
|
|
75
|
+
load_kube_config(str(active.kubeconfig))
|
|
76
|
+
else:
|
|
77
|
+
logger.opt(ansi=True).info(
|
|
78
|
+
"No <yellow>nyl-profiles.yaml</> file found, using default kubeconfig and context."
|
|
79
|
+
)
|
|
80
|
+
load_kube_config()
|
|
81
|
+
return ApiClient()
|
|
82
|
+
|
|
83
|
+
|
|
41
84
|
@app.command()
|
|
42
85
|
def template(
|
|
43
86
|
paths: list[Path] = Argument(..., help="The YAML file(s) to render. Can be a directory."),
|
|
@@ -88,6 +131,10 @@ def template(
|
|
|
88
131
|
help="The directory to store cache data in. If not set, a directory in the --state-dir is used.",
|
|
89
132
|
envvar="NYL_CACHE_DIR",
|
|
90
133
|
),
|
|
134
|
+
on_lookup_failure: OnLookupFailure | None = Option(
|
|
135
|
+
None,
|
|
136
|
+
help="Specify what to do when a lookup() call in a Nyl templated manifest fails. This overrides the nyl-project.toml setting if specified.",
|
|
137
|
+
),
|
|
91
138
|
) -> None:
|
|
92
139
|
"""
|
|
93
140
|
Render a package template into full Kubernetes resources.
|
|
@@ -125,24 +172,10 @@ def template(
|
|
|
125
172
|
# plugin without granting it access to the Kubernetes API. Most relevant bits of information that Nyl requires
|
|
126
173
|
# about the cluster are passed via the environment variables.
|
|
127
174
|
# See https://argo-cd.readthedocs.io/en/stable/user-guide/build-environment/
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
load_incluster_config()
|
|
131
|
-
else:
|
|
132
|
-
with ProfileManager.load(required=False) as profiles:
|
|
133
|
-
# If no profile to activate is specified, and there are no profiles defined, we're not activating a
|
|
134
|
-
# a profile. It should be valid to use Nyl without a `nyl-profiles.yaml` file.
|
|
135
|
-
if profile is not None or profiles.config.profiles:
|
|
136
|
-
profile = profile or DEFAULT_PROFILE
|
|
137
|
-
active = profiles.activate_profile(profile)
|
|
138
|
-
load_kube_config(str(active.kubeconfig))
|
|
139
|
-
else:
|
|
140
|
-
logger.opt(ansi=True).info(
|
|
141
|
-
"No <yellow>nyl-profiles.yaml</> file found, using default kubeconfig and context."
|
|
142
|
-
)
|
|
143
|
-
load_kube_config()
|
|
175
|
+
PROVIDER.set(ApiClientConfig, ApiClientConfig(in_cluster=in_cluster, profile=profile))
|
|
176
|
+
client = PROVIDER.get(ApiClient)
|
|
144
177
|
|
|
145
|
-
project =
|
|
178
|
+
project = PROVIDER.get(ProjectConfig)
|
|
146
179
|
if generate_applysets is not None:
|
|
147
180
|
project.config.settings.generate_applysets = generate_applysets
|
|
148
181
|
|
|
@@ -152,11 +185,14 @@ def template(
|
|
|
152
185
|
if cache_dir is None:
|
|
153
186
|
cache_dir = state_dir / "cache"
|
|
154
187
|
|
|
155
|
-
secrets =
|
|
156
|
-
client = ApiClient()
|
|
188
|
+
secrets = PROVIDER.get(SecretsConfig)
|
|
157
189
|
|
|
158
190
|
template_engine = NylTemplateEngine(
|
|
159
|
-
secrets.providers[secrets_provider],
|
|
191
|
+
secrets.providers[secrets_provider],
|
|
192
|
+
client,
|
|
193
|
+
on_lookup_failure=on_lookup_failure.to_literal()
|
|
194
|
+
if on_lookup_failure
|
|
195
|
+
else project.config.settings.on_lookup_failure,
|
|
160
196
|
)
|
|
161
197
|
|
|
162
198
|
generator = DispatchingGenerator.default(
|