nyl 0.0.0__tar.gz → 0.0.2__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.0.2/PKG-INFO +155 -0
- nyl-0.0.2/README.md +136 -0
- nyl-0.0.2/pyproject.toml +52 -0
- nyl-0.0.2/src/nyl/appmanager/__init__.py +3 -0
- nyl-0.0.2/src/nyl/appmanager/appmanager.py +150 -0
- nyl-0.0.2/src/nyl/appmanager/crd.py +107 -0
- nyl-0.0.2/src/nyl/commands/__init__.py +19 -0
- nyl-0.0.2/src/nyl/commands/profile.py +30 -0
- nyl-0.0.2/src/nyl/commands/secrets.py +31 -0
- nyl-0.0.2/src/nyl/commands/template.py +274 -0
- nyl-0.0.2/src/nyl/commands/tun.py +122 -0
- nyl-0.0.2/src/nyl/packages/__init__.py +3 -0
- nyl-0.0.2/src/nyl/packages/metadata.py +7 -0
- nyl-0.0.2/src/nyl/packages/reference.py +91 -0
- nyl-0.0.2/src/nyl/packages/reference_test.py +12 -0
- nyl-0.0.2/src/nyl/packages/templating.py +52 -0
- nyl-0.0.2/src/nyl/profiles/__init__.py +124 -0
- nyl-0.0.2/src/nyl/profiles/config.py +160 -0
- nyl-0.0.2/src/nyl/profiles/kubeconfig.py +138 -0
- nyl-0.0.2/src/nyl/profiles/tunnel.py +247 -0
- nyl-0.0.2/src/nyl/resources/__init__.py +62 -0
- nyl-0.0.2/src/nyl/resources/helmchart.py +61 -0
- nyl-0.0.2/src/nyl/resources/package.py +17 -0
- nyl-0.0.2/src/nyl/resources/statefulsecret.py +13 -0
- nyl-0.0.2/src/nyl/secrets/__init__.py +1 -0
- nyl-0.0.2/src/nyl/secrets/config.py +80 -0
- nyl-0.0.2/src/nyl/secrets/sops.py +81 -0
- nyl-0.0.2/src/nyl/secrets/sops_test.py +69 -0
- nyl-0.0.2/src/nyl/tools/kubectl.py +130 -0
- nyl-0.0.2/src/nyl/tools/kvstore.py +132 -0
- nyl-0.0.2/src/nyl/tools/typer.py +8 -0
- nyl-0.0.0/PKG-INFO +0 -17
- nyl-0.0.0/README.md +0 -8
- nyl-0.0.0/pyproject.toml +0 -22
- {nyl-0.0.0 → nyl-0.0.2}/src/nyl/__init__.py +0 -0
- {nyl-0.0.0 → nyl-0.0.2}/tests/__init__.py +0 -0
nyl-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nyl
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Default template for PDM package
|
|
5
|
+
Author-Email: Niklas Rosenstein <rosensteinniklas@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: databind>=4.5.2
|
|
9
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
10
|
+
Requires-Dist: jinja2>=3.1.4
|
|
11
|
+
Requires-Dist: loguru>=0.7.2
|
|
12
|
+
Requires-Dist: kubernetes>=30.1.0
|
|
13
|
+
Requires-Dist: typer>=0.12.3
|
|
14
|
+
Requires-Dist: filelock>=3.15.4
|
|
15
|
+
Requires-Dist: stablehash>=0.2.0
|
|
16
|
+
Requires-Dist: nr-stream>=1.1.5
|
|
17
|
+
Requires-Dist: requests>=2.32.3
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# nyl
|
|
21
|
+
|
|
22
|
+
Nyl is a versatile Kubernetes resource management tool that fulfills various functions to enable reusable
|
|
23
|
+
deployment configurations.
|
|
24
|
+
|
|
25
|
+
__Features__
|
|
26
|
+
|
|
27
|
+
* Generate Kubernetes manifests from a simple YAML configuration with templating support.
|
|
28
|
+
* Inject secrets into Kubernetes manifests as time of generation.
|
|
29
|
+
* Bootstrap a cluster and transfer control to ArgoCD or use it to manage a cluster from CI/CD or locally.
|
|
30
|
+
* Connect to a Kubernetes cluster via an SSH tunnel.
|
|
31
|
+
|
|
32
|
+
__Roadmap__
|
|
33
|
+
|
|
34
|
+
* Integrate as an ArgoCD ConfigManagementPlugin.
|
|
35
|
+
* Diffing
|
|
36
|
+
* How to work around fields owned by a different field manager (e.g. MetalLB CRD `conversion..caBundle`) or `Job` resources?
|
|
37
|
+
|
|
38
|
+
## Concepts
|
|
39
|
+
|
|
40
|
+
### Packages
|
|
41
|
+
|
|
42
|
+
A package is similar to a Helm chart in that it is a source for Kubernetes resources that can make use of
|
|
43
|
+
templating to conditionally render resources and inject values into them. Resources generated by packages
|
|
44
|
+
may make use of Nyl-specific resource kinds that are only available time of resource generation (see the
|
|
45
|
+
[Templating > Resource Kinds](#resource-kinds) section below).
|
|
46
|
+
|
|
47
|
+
Nyl packages can be checked into a source repository in a DRY form, but can be compiled to a bundle (e.g.
|
|
48
|
+
including other referenced packages or Helm charts) for deployment when needed (e.g. for shipping to an
|
|
49
|
+
air-gapped environment).
|
|
50
|
+
|
|
51
|
+
Packages may have a `nyl-package.yaml` file that defines a schema for the package parameters and additional
|
|
52
|
+
metadata. A package without this file may still accept and use parameters in its templates.
|
|
53
|
+
|
|
54
|
+
### Applications
|
|
55
|
+
|
|
56
|
+
An application is an instance of one package that is deployed to a Kubernetes cluster and are in turn defined
|
|
57
|
+
also in a Nyl package. Packages instantiated as applications may not produce other applications. Applications
|
|
58
|
+
are usually accompanied by a `nyl-deployment.yaml` file that defines the top-level templating context for the
|
|
59
|
+
package(s), such as the secret store.
|
|
60
|
+
|
|
61
|
+
When deploying a package as an application, the package must not generated resources other than applications,
|
|
62
|
+
as all deployed resources must be owned by an application.
|
|
63
|
+
|
|
64
|
+
### Deployment
|
|
65
|
+
|
|
66
|
+
A deployment is where things get together: This is the place where you define the applications that should be
|
|
67
|
+
deployed to a Kubernetes cluster, which cluster they are being deployed to, as well as sourcing secrets from
|
|
68
|
+
a secret store. The configuration for a deployment is defined in a `nyl-deployment.yaml` file.
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
apiVersion: nyl/v1
|
|
72
|
+
kind: Deployment
|
|
73
|
+
spec:
|
|
74
|
+
secretStores:
|
|
75
|
+
default:
|
|
76
|
+
type: Sops
|
|
77
|
+
path: secrets.yaml
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
__Spec__
|
|
81
|
+
|
|
82
|
+
* `secrets` (array): The secret stores to make available in the templating context of the deployment. Any secrets
|
|
83
|
+
need to be injected from these stores into the application values at templating time.
|
|
84
|
+
|
|
85
|
+
## Templating
|
|
86
|
+
|
|
87
|
+
[Jinja2]: https://jinja.palletsprojects.com/en/3.0.x/
|
|
88
|
+
|
|
89
|
+
Nyl uses [Jinja2] as a templating engine, and as such is slightly different from Helm.
|
|
90
|
+
|
|
91
|
+
### Injecting secrets
|
|
92
|
+
|
|
93
|
+
Secrets are only available at the deployment level and need to be propagated further down.
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
apiVersion: nyl/v1
|
|
97
|
+
kind: Application
|
|
98
|
+
metadata:
|
|
99
|
+
name: my-app
|
|
100
|
+
spec:
|
|
101
|
+
package: ./path/to/package
|
|
102
|
+
values:
|
|
103
|
+
theSecret: {{ Secrets.default.get("my-secret") }}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Resource kinds
|
|
107
|
+
|
|
108
|
+
At templating time, Nyl supports special resource kinds that will be expanded to more Kubernetes resources.
|
|
109
|
+
|
|
110
|
+
#### `templating.nyl/v1/HelmChart`
|
|
111
|
+
|
|
112
|
+
This resource can be used to instantiate Helm charts as part of a package.
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
apiVersion: templating.nyl/v1
|
|
116
|
+
kind: HelmChart
|
|
117
|
+
spec:
|
|
118
|
+
repository: https://kubernetes.github.io/ingress-nginx
|
|
119
|
+
chart: ingress-nginx
|
|
120
|
+
version: 4.10.1
|
|
121
|
+
releaseName: ingress-nginx
|
|
122
|
+
releaseNamespace: ingress-nginx
|
|
123
|
+
values: {}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
__Spec__
|
|
127
|
+
|
|
128
|
+
* `repository` (string): The Helm repository URL, OCI registry URL (sans the chart name), a local path or a
|
|
129
|
+
Git repository clone URL.
|
|
130
|
+
* `chart` (string): The name of the chart in the repository, the chart name to append to the OCI registry URL,
|
|
131
|
+
the name of the chart in the local path or the path to the chart in the Git repository.
|
|
132
|
+
* `version` (string): The version of the chart to use. Only relevant for Helm/OCI repositories.
|
|
133
|
+
* `releaseName` (string): The name of the Helm release.
|
|
134
|
+
* `releaseNamespace` (string): The namespace to install the Helm release into. If any resources generated by the
|
|
135
|
+
chart have no `metadata.namespace` field, it will be set to this value.
|
|
136
|
+
* `values` (object): The values to pass to the Helm chart.
|
|
137
|
+
|
|
138
|
+
#### `templating.nyl/v1/Package`
|
|
139
|
+
|
|
140
|
+
Use this resource to instantiate another package.
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
apiVersion: templating.nyl/v1
|
|
144
|
+
kind: Package
|
|
145
|
+
spec:
|
|
146
|
+
package: ./path/to/package
|
|
147
|
+
values: {}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
__Spec__
|
|
151
|
+
|
|
152
|
+
* `package` (string): The path to the package to instantiate. Must be prefixed with `./` to be resolved relative
|
|
153
|
+
to the package that references it. Otherwise, it will be resolved in a search path that is defined in the
|
|
154
|
+
`nyl-deployment.yaml` file.
|
|
155
|
+
* `values` (object): The values to pass to the package.
|
nyl-0.0.2/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# nyl
|
|
2
|
+
|
|
3
|
+
Nyl is a versatile Kubernetes resource management tool that fulfills various functions to enable reusable
|
|
4
|
+
deployment configurations.
|
|
5
|
+
|
|
6
|
+
__Features__
|
|
7
|
+
|
|
8
|
+
* Generate Kubernetes manifests from a simple YAML configuration with templating support.
|
|
9
|
+
* Inject secrets into Kubernetes manifests as time of generation.
|
|
10
|
+
* Bootstrap a cluster and transfer control to ArgoCD or use it to manage a cluster from CI/CD or locally.
|
|
11
|
+
* Connect to a Kubernetes cluster via an SSH tunnel.
|
|
12
|
+
|
|
13
|
+
__Roadmap__
|
|
14
|
+
|
|
15
|
+
* Integrate as an ArgoCD ConfigManagementPlugin.
|
|
16
|
+
* Diffing
|
|
17
|
+
* How to work around fields owned by a different field manager (e.g. MetalLB CRD `conversion..caBundle`) or `Job` resources?
|
|
18
|
+
|
|
19
|
+
## Concepts
|
|
20
|
+
|
|
21
|
+
### Packages
|
|
22
|
+
|
|
23
|
+
A package is similar to a Helm chart in that it is a source for Kubernetes resources that can make use of
|
|
24
|
+
templating to conditionally render resources and inject values into them. Resources generated by packages
|
|
25
|
+
may make use of Nyl-specific resource kinds that are only available time of resource generation (see the
|
|
26
|
+
[Templating > Resource Kinds](#resource-kinds) section below).
|
|
27
|
+
|
|
28
|
+
Nyl packages can be checked into a source repository in a DRY form, but can be compiled to a bundle (e.g.
|
|
29
|
+
including other referenced packages or Helm charts) for deployment when needed (e.g. for shipping to an
|
|
30
|
+
air-gapped environment).
|
|
31
|
+
|
|
32
|
+
Packages may have a `nyl-package.yaml` file that defines a schema for the package parameters and additional
|
|
33
|
+
metadata. A package without this file may still accept and use parameters in its templates.
|
|
34
|
+
|
|
35
|
+
### Applications
|
|
36
|
+
|
|
37
|
+
An application is an instance of one package that is deployed to a Kubernetes cluster and are in turn defined
|
|
38
|
+
also in a Nyl package. Packages instantiated as applications may not produce other applications. Applications
|
|
39
|
+
are usually accompanied by a `nyl-deployment.yaml` file that defines the top-level templating context for the
|
|
40
|
+
package(s), such as the secret store.
|
|
41
|
+
|
|
42
|
+
When deploying a package as an application, the package must not generated resources other than applications,
|
|
43
|
+
as all deployed resources must be owned by an application.
|
|
44
|
+
|
|
45
|
+
### Deployment
|
|
46
|
+
|
|
47
|
+
A deployment is where things get together: This is the place where you define the applications that should be
|
|
48
|
+
deployed to a Kubernetes cluster, which cluster they are being deployed to, as well as sourcing secrets from
|
|
49
|
+
a secret store. The configuration for a deployment is defined in a `nyl-deployment.yaml` file.
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
apiVersion: nyl/v1
|
|
53
|
+
kind: Deployment
|
|
54
|
+
spec:
|
|
55
|
+
secretStores:
|
|
56
|
+
default:
|
|
57
|
+
type: Sops
|
|
58
|
+
path: secrets.yaml
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
__Spec__
|
|
62
|
+
|
|
63
|
+
* `secrets` (array): The secret stores to make available in the templating context of the deployment. Any secrets
|
|
64
|
+
need to be injected from these stores into the application values at templating time.
|
|
65
|
+
|
|
66
|
+
## Templating
|
|
67
|
+
|
|
68
|
+
[Jinja2]: https://jinja.palletsprojects.com/en/3.0.x/
|
|
69
|
+
|
|
70
|
+
Nyl uses [Jinja2] as a templating engine, and as such is slightly different from Helm.
|
|
71
|
+
|
|
72
|
+
### Injecting secrets
|
|
73
|
+
|
|
74
|
+
Secrets are only available at the deployment level and need to be propagated further down.
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
apiVersion: nyl/v1
|
|
78
|
+
kind: Application
|
|
79
|
+
metadata:
|
|
80
|
+
name: my-app
|
|
81
|
+
spec:
|
|
82
|
+
package: ./path/to/package
|
|
83
|
+
values:
|
|
84
|
+
theSecret: {{ Secrets.default.get("my-secret") }}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Resource kinds
|
|
88
|
+
|
|
89
|
+
At templating time, Nyl supports special resource kinds that will be expanded to more Kubernetes resources.
|
|
90
|
+
|
|
91
|
+
#### `templating.nyl/v1/HelmChart`
|
|
92
|
+
|
|
93
|
+
This resource can be used to instantiate Helm charts as part of a package.
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
apiVersion: templating.nyl/v1
|
|
97
|
+
kind: HelmChart
|
|
98
|
+
spec:
|
|
99
|
+
repository: https://kubernetes.github.io/ingress-nginx
|
|
100
|
+
chart: ingress-nginx
|
|
101
|
+
version: 4.10.1
|
|
102
|
+
releaseName: ingress-nginx
|
|
103
|
+
releaseNamespace: ingress-nginx
|
|
104
|
+
values: {}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
__Spec__
|
|
108
|
+
|
|
109
|
+
* `repository` (string): The Helm repository URL, OCI registry URL (sans the chart name), a local path or a
|
|
110
|
+
Git repository clone URL.
|
|
111
|
+
* `chart` (string): The name of the chart in the repository, the chart name to append to the OCI registry URL,
|
|
112
|
+
the name of the chart in the local path or the path to the chart in the Git repository.
|
|
113
|
+
* `version` (string): The version of the chart to use. Only relevant for Helm/OCI repositories.
|
|
114
|
+
* `releaseName` (string): The name of the Helm release.
|
|
115
|
+
* `releaseNamespace` (string): The namespace to install the Helm release into. If any resources generated by the
|
|
116
|
+
chart have no `metadata.namespace` field, it will be set to this value.
|
|
117
|
+
* `values` (object): The values to pass to the Helm chart.
|
|
118
|
+
|
|
119
|
+
#### `templating.nyl/v1/Package`
|
|
120
|
+
|
|
121
|
+
Use this resource to instantiate another package.
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
apiVersion: templating.nyl/v1
|
|
125
|
+
kind: Package
|
|
126
|
+
spec:
|
|
127
|
+
package: ./path/to/package
|
|
128
|
+
values: {}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
__Spec__
|
|
132
|
+
|
|
133
|
+
* `package` (string): The path to the package to instantiate. Must be prefixed with `./` to be resolved relative
|
|
134
|
+
to the package that references it. Otherwise, it will be resolved in a search path that is defined in the
|
|
135
|
+
`nyl-deployment.yaml` file.
|
|
136
|
+
* `values` (object): The values to pass to the package.
|
nyl-0.0.2/pyproject.toml
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "nyl"
|
|
3
|
+
version = "0.0.2"
|
|
4
|
+
description = "Default template for PDM package"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Niklas Rosenstein", email = "rosensteinniklas@gmail.com" },
|
|
7
|
+
]
|
|
8
|
+
dependencies = [
|
|
9
|
+
"databind>=4.5.2",
|
|
10
|
+
"pyyaml>=6.0.1",
|
|
11
|
+
"jinja2>=3.1.4",
|
|
12
|
+
"loguru>=0.7.2",
|
|
13
|
+
"kubernetes>=30.1.0",
|
|
14
|
+
"typer>=0.12.3",
|
|
15
|
+
"filelock>=3.15.4",
|
|
16
|
+
"stablehash>=0.2.0",
|
|
17
|
+
"nr-stream>=1.1.5",
|
|
18
|
+
"requests>=2.32.3",
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.10"
|
|
21
|
+
readme = "README.md"
|
|
22
|
+
|
|
23
|
+
[project.license]
|
|
24
|
+
text = "MIT"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
nyl = "nyl.commands:app"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = [
|
|
31
|
+
"pdm-backend",
|
|
32
|
+
]
|
|
33
|
+
build-backend = "pdm.backend"
|
|
34
|
+
|
|
35
|
+
[tool.pdm]
|
|
36
|
+
distribution = true
|
|
37
|
+
|
|
38
|
+
[tool.pdm.dev-dependencies]
|
|
39
|
+
dev = [
|
|
40
|
+
"ruff>=0.4.8",
|
|
41
|
+
"mypy>=1.10.0",
|
|
42
|
+
"types-pyyaml>=6.0.12.20240311",
|
|
43
|
+
"mkdocs>=1.6.0",
|
|
44
|
+
"mkdocs-material>=9.5.28",
|
|
45
|
+
"mkdocstrings[python]>=0.25.1",
|
|
46
|
+
"pytest>=8.2.2",
|
|
47
|
+
"kubernetes-stubs>=22.6.0.post1",
|
|
48
|
+
"types-requests>=2.32.0.20240712",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 120
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
from kubernetes.client import CustomObjectsApi
|
|
4
|
+
from kubernetes.client.api_client import ApiClient
|
|
5
|
+
from . import crd
|
|
6
|
+
from nyl.tools.kubectl import Kubectl
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Application:
|
|
11
|
+
"""
|
|
12
|
+
Represents an application deployed in the system.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
name: str
|
|
16
|
+
contains_group_kinds: list[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ApplicationManager:
|
|
20
|
+
"""
|
|
21
|
+
Manage applications deployed in the system.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, client: ApiClient, kubectl: Kubectl) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Args:
|
|
27
|
+
client: Kubernetes API client.
|
|
28
|
+
kubectl: A corresponding `kubectl` client to interact with the cluster. We re-use kubectl's applset
|
|
29
|
+
implementation to create and manage applications in the system.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
self._client = client
|
|
33
|
+
self._kubectl = kubectl
|
|
34
|
+
self._kubectl_version_cached: str | None = None
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def _kubectl_version(self) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Get the version of the kubectl client.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
if self._kubectl_version_cached is None:
|
|
43
|
+
self._kubectl_version_cached = self._kubectl.version()["gitVersion"]
|
|
44
|
+
return self._kubectl_version_cached
|
|
45
|
+
|
|
46
|
+
def _get_applyset_ref(self, name: str) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Get the reference to the ApplySet resource for the application.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
return f"application.{crd.GROUP}/{name}"
|
|
52
|
+
|
|
53
|
+
def list_applications(self) -> Application:
|
|
54
|
+
"""
|
|
55
|
+
List applications deployed in the system.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
api = CustomObjectsApi(self._client)
|
|
59
|
+
objects = api.list_cluster_custom_object( # noqa
|
|
60
|
+
group=crd.GROUP,
|
|
61
|
+
version=crd.VERSION,
|
|
62
|
+
plural=crd.PLURAL,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# TODO
|
|
66
|
+
return # type: ignore[return-value]
|
|
67
|
+
|
|
68
|
+
def get_application(self, name: str) -> Application:
|
|
69
|
+
"""
|
|
70
|
+
Get an application deployed in the system.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
api = CustomObjectsApi(self._client)
|
|
74
|
+
obj = api.get_cluster_custom_object(group=crd.GROUP, version=crd.VERSION, plural=crd.PLURAL, name=name)
|
|
75
|
+
print(obj)
|
|
76
|
+
# TODO
|
|
77
|
+
return # type: ignore[return-value]
|
|
78
|
+
|
|
79
|
+
def delete_application(self, name: str, cascade: bool = True) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Delete an application from the system.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
name: The name of the application to delete.
|
|
85
|
+
cascade: If True, delete all resources associated with the application.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
if cascade:
|
|
89
|
+
self._kubectl.apply(
|
|
90
|
+
manifests=[],
|
|
91
|
+
force_conflicts=True,
|
|
92
|
+
server_side=True,
|
|
93
|
+
applyset=self._get_applyset_ref(name),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
api = CustomObjectsApi(self._client)
|
|
97
|
+
api.delete_cluster_custom_object(group=crd.GROUP, version=crd.VERSION, plural=crd.PLURAL, name=name)
|
|
98
|
+
|
|
99
|
+
# def upsert_application(self, app: Application) -> None:
|
|
100
|
+
# """
|
|
101
|
+
# Create or update an application in the system.
|
|
102
|
+
# """
|
|
103
|
+
|
|
104
|
+
# api = CustomObjectsApi(self._client)
|
|
105
|
+
# api.create_cluster_custom_object(
|
|
106
|
+
# group=crd.GROUP,
|
|
107
|
+
# version=crd.VERSION,
|
|
108
|
+
# plural=crd.PLURAL,
|
|
109
|
+
# body=crd.generate_application_resource(
|
|
110
|
+
# app.name,
|
|
111
|
+
# contains_resources=app.contains_group_kinds,
|
|
112
|
+
# kubectl_version=self._kubectl_version,
|
|
113
|
+
# ),
|
|
114
|
+
# )
|
|
115
|
+
|
|
116
|
+
def update_application(self, name: str, manifests: list[dict[str, Any]]) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Update an application in the system or create it.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
name: The name of the application to update.
|
|
122
|
+
manifests: The updated manifests of the application.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
resource_kinds = {crd.get_canonical_resource_kind_name(m["apiVersion"], m["kind"]) for m in manifests}
|
|
126
|
+
|
|
127
|
+
# Ensure that the application object exists.
|
|
128
|
+
# TODO Check if it exists instead of always calling the create endpoint?
|
|
129
|
+
# TODO Do we maybe need to update the resource kinds only after the apply?
|
|
130
|
+
# Somehow kubectl must understand the previous resource kinds that were involved in the
|
|
131
|
+
# application prior to the update.
|
|
132
|
+
|
|
133
|
+
api = CustomObjectsApi(self._client)
|
|
134
|
+
api.create_cluster_custom_object(
|
|
135
|
+
group=crd.GROUP,
|
|
136
|
+
version=crd.VERSION,
|
|
137
|
+
plural=crd.PLURAL,
|
|
138
|
+
body=crd.generate_application_resource(
|
|
139
|
+
name,
|
|
140
|
+
contains_resources=resource_kinds,
|
|
141
|
+
kubectl_version=self._kubectl_version,
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
self._kubectl.apply(
|
|
146
|
+
manifests=manifests,
|
|
147
|
+
force_conflicts=True,
|
|
148
|
+
server_side=True,
|
|
149
|
+
applyset=self._get_applyset_ref(name),
|
|
150
|
+
)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module implements a Kubernetes CRD that represents Nyl's own understanding of an application, which also
|
|
3
|
+
acts as a Kubernetes ApplySet. To read more about ApplySets, check out
|
|
4
|
+
|
|
5
|
+
https://kubernetes.io/blog/2023/05/09/introducing-kubectl-applyset-pruning/.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import base64
|
|
9
|
+
import hashlib
|
|
10
|
+
from typing import Any, Collection
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
GROUP = "nyl.io"
|
|
14
|
+
KIND = "Application"
|
|
15
|
+
PLURAL = "applications"
|
|
16
|
+
VERSION = "v1"
|
|
17
|
+
|
|
18
|
+
# note: the only purpose of this CRD is to create resources that act as a parent for ApplySets.
|
|
19
|
+
# check out this GitHub issue, and specifically this comment for more information:
|
|
20
|
+
# https://github.com/kubernetes/enhancements/issues/3659#issuecomment-1753091733
|
|
21
|
+
APPLICATION_CRD = {
|
|
22
|
+
"apiVersion": "apiextensions.k8s.io/v1",
|
|
23
|
+
"kind": "CustomResourceDefinition",
|
|
24
|
+
"metadata": {
|
|
25
|
+
"name": f"{PLURAL}.{GROUP}",
|
|
26
|
+
"labels": {
|
|
27
|
+
"applyset.kubernetes.io/is-parent-type": "true",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
"spec": {
|
|
31
|
+
"group": GROUP,
|
|
32
|
+
"names": {
|
|
33
|
+
"kind": KIND,
|
|
34
|
+
"plural": PLURAL,
|
|
35
|
+
},
|
|
36
|
+
"scope": "Cluster",
|
|
37
|
+
"versions": [
|
|
38
|
+
{
|
|
39
|
+
"name": VERSION,
|
|
40
|
+
"served": True,
|
|
41
|
+
"storage": True,
|
|
42
|
+
"schema": {
|
|
43
|
+
"openAPIV3Schema": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def calculate_applyset_id(*, name: str, namespace: str = "", group: str) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Calculate the ID of a Kubernetes ApplySet with the specified name.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# reference: https://kubernetes.io/docs/reference/labels-annotations-taints/#applyset-kubernetes-io-id
|
|
59
|
+
hash = hashlib.sha256(f"{name}.{namespace}.ApplySet.{group}".encode()).digest()
|
|
60
|
+
uid = base64.b64encode(hash).decode().rstrip("=").replace("/", "_").replace("+", "-")
|
|
61
|
+
return f"applyset-{uid}-v1"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_canonical_resource_kind_name(api_version: str, kind: str) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Given the apiVersion and kind of a Kubernetes resource, return the canonical name of the resource. This name can
|
|
67
|
+
be used to identify the resource in an ApplySet's `applyset.kubernetes.io/contains-group-kinds` annotation.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
api_version: The apiVersion of the resource.
|
|
71
|
+
kind: The kind of the resource.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
return (f"{kind}." + (api_version.split("/")[0] if "/" in api_version else "")).rstrip(".")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def generate_application_resource(
|
|
78
|
+
name: str,
|
|
79
|
+
contains_resources: Collection[str],
|
|
80
|
+
kubectl_version: str,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
Generate a Kubernetes resource that represents an application in Nyl's system.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
name: The name of the application.
|
|
87
|
+
contains_resources: A list of Kubernetes resources names that are contained in this application. This is a
|
|
88
|
+
required information for Kubernetes ApplySets. You can use `get_canonical_resource_kind_name` to get the
|
|
89
|
+
names of the resource kinds.
|
|
90
|
+
kubectl_version: The version of kubectl that was used to create this resource. This is a required information
|
|
91
|
+
for the `applyset.kubernetes.io/tooling` annotation in the resource if `kubectl` is used to apply it.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
"apiVersion": f"{GROUP}/{VERSION}",
|
|
96
|
+
"kind": KIND,
|
|
97
|
+
"metadata": {
|
|
98
|
+
"name": name,
|
|
99
|
+
"annotations": {
|
|
100
|
+
"applyset.kubernetes.io/tooling": f"kubectl/{kubectl_version}",
|
|
101
|
+
"applyset.kubernetes.io/contains-group-kinds": ",".join(sorted(contains_resources)),
|
|
102
|
+
},
|
|
103
|
+
"labels": {
|
|
104
|
+
"applyset.kubernetes.io/id": calculate_applyset_id(name=name, group=GROUP),
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nyl is a flexible configuration management tool for Kubernetes resources that can be used to generate and deploy
|
|
3
|
+
applications directly or integrate as an ArgoCD ConfigManagementPlugin.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from nyl.tools.typer import new_typer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
app = new_typer(help=__doc__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from . import profile # noqa: E402
|
|
13
|
+
from . import secrets # noqa: E402
|
|
14
|
+
from . import template # noqa: F401,E402
|
|
15
|
+
from . import tun # noqa: E402
|
|
16
|
+
|
|
17
|
+
app.add_typer(profile.app)
|
|
18
|
+
app.add_typer(secrets.app)
|
|
19
|
+
app.add_typer(tun.app)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interact with your Nyl profile configuration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import shlex
|
|
6
|
+
|
|
7
|
+
from typer import Argument
|
|
8
|
+
from nyl.profiles import ProfileManager
|
|
9
|
+
from nyl.tools.typer import new_typer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
app = new_typer(name="profile", help=__doc__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def activate(profile_name: str = Argument("default", envvar="NYL_PROFILE")) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Activate a Nyl profile.
|
|
19
|
+
|
|
20
|
+
Evaluate the stdout of this command to export the KUBECONFIG into your environment.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
with ProfileManager.load() as manager:
|
|
24
|
+
profile = manager.activate_profile(profile_name)
|
|
25
|
+
|
|
26
|
+
# For standard tooling, like kubectl.
|
|
27
|
+
print(f"export KUBECONFIG={shlex.quote(str(profile.kubeconfig.absolute()))}")
|
|
28
|
+
|
|
29
|
+
# Used by Terraform providers.
|
|
30
|
+
print(f"export KUBE_CONFIG_PATH={shlex.quote(str(profile.kubeconfig.absolute()))}")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interact with the secrets providers configured in `nyl-secrets.yaml`.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from nyl.secrets.config import SecretsConfig
|
|
7
|
+
from nyl.tools.typer import new_typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
app = new_typer(name="secrets", help=__doc__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command()
|
|
14
|
+
def list() -> None:
|
|
15
|
+
"""
|
|
16
|
+
List the keys for all secrets in the provider.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
secrets = SecretsConfig.load(SecretsConfig.find_config_file())
|
|
20
|
+
for key in secrets.provider.keys():
|
|
21
|
+
print(key)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command()
|
|
25
|
+
def get(key: str, pretty: bool = False) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Get the value of a secret as JSON.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
secrets = SecretsConfig.load(SecretsConfig.find_config_file())
|
|
31
|
+
print(json.dumps(secrets.provider.get(key), indent=4 if pretty else None))
|