Skip to content

Commit a8ffffc

Browse files
authored
Generate Dhall schemas for multiple Kubernetes versions (#104)
Fixes #75 This also changes the default version to the latest version to 1.17 since now users can access older versions
1 parent eb001e6 commit a8ffffc

File tree

13,047 files changed

+68917
-3368
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

13,047 files changed

+68917
-3368
lines changed

1.12/README.md

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
# `dhall-kubernetes`
2+
3+
<img src="logo/dhall-kubernetes-logo.svg" alt="dhall-kubernetes logo" height="300px"/>
4+
5+
`dhall-kubernetes` contains [Dhall][dhall-lang] bindings to [Kubernetes][kubernetes],
6+
so you can generate Kubernetes objects definitions from Dhall expressions.
7+
This will let you easily typecheck, template and modularize your Kubernetes definitions.
8+
9+
## Why do I need this
10+
11+
Once you build a slightly non-trivial Kubernetes setup, with many objects floating
12+
around, you'll encounter several issues:
13+
1. Writing the definitions in YAML is really verbose, and the actually important
14+
things don't stand out that much
15+
2. Ok I have a bunch of objects that'll need to be configured together, how do I share data?
16+
3. I'd like to reuse an object for different environments, but I cannot make it parametric..
17+
4. In general, I'd really love to reuse parts of some definitions in other definitions
18+
5. Oh no, I typoed a key and I had to wait until I pushed to the cluster to get an error back :(
19+
20+
The natural tendency is to reach for a templating language + a programming language to orchestrate that + some more configuration for it...
21+
But this is just really messy (been there), and we can do better.
22+
23+
Dhall solves all of this, being a programming language with builtin templating,
24+
all while being non-Turing complete, strongly typed and [strongly normalizing][normalization]
25+
(i.e.: reduces everything to a normal form, no matter how much abstraction you build),
26+
so saving you from the *"oh-noes-I-made-my-config-in-code-and-now-its-too-abstract"* nightmare.
27+
28+
For a Dhall Tutorial, see [the website][dhall-website], or the [readme of the project][dhall-lang],
29+
or the [full tutorial][dhall-tutorial].
30+
31+
## Prerequisites
32+
33+
**NOTE**: `dhall-kubernetes` requires at least version `1.27.0` of [the interpreter](https://github.com/dhall-lang/dhall-haskell)
34+
(version `11.0.0` of the language).
35+
36+
## Quickstart - a simple Deployment
37+
38+
Let's say we'd like to configure a Deployment exposing an `nginx` webserver.
39+
40+
In the following example, we:
41+
1. Import the Kubernetes definitions as a Dhall package (the `package.dhall` file) from the local repo.
42+
In your case you will want to replace the local path with a remote one, e.g.
43+
`https://raw.githubusercontent.com/dhall-lang/dhall-kubernetes/master/package.dhall`
44+
Note: the `sha256:..` is applied to some imports so that:
45+
1. the import is cached locally after the first evaluation, with great time savings (and avoiding network calls)
46+
2. prevent execution if the content of the file changes. This is a security feature, and you
47+
can read more [in Dhall's "Security Guarantees" document][security-hashes]
48+
Note: instead of using the `package.dhall` from the `master` branch, you may want to use a tagged release,
49+
as the contents of the `master` branch are liable to change without warning.
50+
2. Define the [Deployment][deployment] using the schema pattern and hardcoding the deployment details:
51+
52+
```dhall
53+
-- examples/deploymentSimple.dhall
54+
55+
let kubernetes =
56+
../package.dhall sha256:ab1c971ddeb178c1cfc5e749b211b4fe6fdb6fa1b68b10de62aeb543efcd60b3
57+
58+
let deployment =
59+
kubernetes.Deployment::{
60+
, metadata = kubernetes.ObjectMeta::{ name = "nginx" }
61+
, spec =
62+
Some
63+
kubernetes.DeploymentSpec::{
64+
, replicas = Some 2
65+
, template =
66+
kubernetes.PodTemplateSpec::{
67+
, metadata = kubernetes.ObjectMeta::{ name = "nginx" }
68+
, spec =
69+
Some
70+
kubernetes.PodSpec::{
71+
, containers =
72+
[ kubernetes.Container::{
73+
, name = "nginx"
74+
, image = Some "nginx:1.15.3"
75+
, ports =
76+
[ kubernetes.ContainerPort::{
77+
, containerPort = 80
78+
}
79+
]
80+
}
81+
]
82+
}
83+
}
84+
}
85+
}
86+
87+
in deployment
88+
89+
```
90+
91+
We then run this through `dhall-to-yaml` to generate our Kubernetes definition:
92+
93+
```bash
94+
dhall-to-yaml --omitEmpty < examples/deploymentSimple.dhall
95+
```
96+
97+
And we get:
98+
```yaml
99+
## examples/out/deploymentSimple.yaml
100+
101+
apiVersion: apps/v1
102+
kind: Deployment
103+
metadata:
104+
name: nginx
105+
spec:
106+
replicas: 2
107+
template:
108+
metadata:
109+
name: nginx
110+
spec:
111+
containers:
112+
- image: nginx:1.15.3
113+
name: nginx
114+
ports:
115+
- containerPort: 80
116+
117+
```
118+
119+
## More modular: defining an Ingress
120+
121+
The above is cool, but hardcoding data is not that cool.
122+
123+
So in a more realistic deployment you'll probably want to define:
124+
- some `MyService` type that contains the config settings relevant to your deployments
125+
- some functions parametrized by this type, so that you can produce objects to send to k8s
126+
by just applying these functions to `MyService` objects
127+
128+
This is useful because then you can define your `Service`s separately from the Kubernetes logic,
129+
and reuse those objects for configuring other things (e.g. configuring the services themselves,
130+
templating documentation, configuring Terraform deployments, you name it).
131+
132+
As an example of that, next we'll define an Ingress (an [Nginx Ingress][nginx-ingress] in this case),
133+
containing stuff like TLS certs and routes for every service - see the [schema][Ingress].
134+
135+
Things to note in the following example:
136+
- we define the `Service` type inline in the file, but in your case you'll want to have a
137+
separate `./Service.dhall` file (so you can share around the project)
138+
- we define functions to create the TLS definitions and the routes, so that we can `map`
139+
them over the list of services.
140+
- we also defined the list of `services` inline, but you should instead return the
141+
`mkIngress` function instead of applying it, so you can do something like
142+
`dhall-to-yaml --omitEmpty <<< "./mkIngress.dhall ./myServices.dhall"`
143+
144+
```dhall
145+
-- examples/ingress.dhall
146+
147+
let Prelude =
148+
../Prelude.dhall sha256:771c7131fc87e13eb18f770a27c59f9418879f7e230ba2a50e46f4461f43ec69
149+
150+
let map = Prelude.List.map
151+
152+
let kv = Prelude.JSON.keyText
153+
154+
let kubernetes =
155+
../package.dhall sha256:ab1c971ddeb178c1cfc5e749b211b4fe6fdb6fa1b68b10de62aeb543efcd60b3
156+
157+
let Service = { name : Text, host : Text, version : Text }
158+
159+
let services = [ { name = "foo", host = "foo.example.com", version = "2.3" } ]
160+
161+
let makeTLS
162+
: Service kubernetes.IngressTLS.Type
163+
= λ(service : Service)
164+
{ hosts = [ service.host ]
165+
, secretName = Some "${service.name}-certificate"
166+
}
167+
168+
let makeRule
169+
: Service kubernetes.IngressRule.Type
170+
= λ(service : Service)
171+
{ host = Some service.host
172+
, http =
173+
Some
174+
{ paths =
175+
[ { backend =
176+
{ serviceName = service.name
177+
, servicePort = kubernetes.IntOrString.Int 80
178+
}
179+
, path = None Text
180+
}
181+
]
182+
}
183+
}
184+
185+
let mkIngress
186+
: List Service kubernetes.Ingress.Type
187+
= λ(inputServices : List Service)
188+
let annotations =
189+
[ kv "kubernetes.io/ingress.class" "nginx"
190+
, kv "kubernetes.io/ingress.allow-http" "false"
191+
]
192+
193+
let defaultService =
194+
{ name = "default"
195+
, host = "default.example.com"
196+
, version = " 1.0"
197+
}
198+
199+
let ingressServices = inputServices # [ defaultService ]
200+
201+
let spec =
202+
kubernetes.IngressSpec::{
203+
, tls =
204+
map Service kubernetes.IngressTLS.Type makeTLS ingressServices
205+
, rules =
206+
map
207+
Service
208+
kubernetes.IngressRule.Type
209+
makeRule
210+
ingressServices
211+
}
212+
213+
in kubernetes.Ingress::{
214+
, metadata =
215+
kubernetes.ObjectMeta::{
216+
, name = "nginx"
217+
, annotations = annotations
218+
}
219+
, spec = Some spec
220+
}
221+
222+
in mkIngress services
223+
224+
```
225+
226+
As before we get the yaml out by running:
227+
228+
```bash
229+
dhall-to-yaml --omitEmpty < examples/ingress.dhall
230+
```
231+
232+
Result:
233+
```yaml
234+
## examples/out/ingress.yaml
235+
236+
apiVersion: networking.k8s.io/v1beta1
237+
kind: Ingress
238+
metadata:
239+
annotations:
240+
kubernetes.io/ingress.allow-http: "false"
241+
kubernetes.io/ingress.class: nginx
242+
name: nginx
243+
spec:
244+
rules:
245+
- host: foo.example.com
246+
http:
247+
paths:
248+
- backend:
249+
serviceName: foo
250+
servicePort: 80
251+
- host: default.example.com
252+
http:
253+
paths:
254+
- backend:
255+
serviceName: default
256+
servicePort: 80
257+
tls:
258+
- hosts:
259+
- foo.example.com
260+
secretName: foo-certificate
261+
- hosts:
262+
- default.example.com
263+
secretName: default-certificate
264+
265+
```
266+
267+
## FAQ
268+
269+
#### Can I generate a YAML file with many objects in it?
270+
271+
It is usual for k8s YAML files to include multiple objects separated by `---` ("documents" in YAML lingo),
272+
so you might want to do it too.
273+
274+
If the objects have the same type, this is very easy: you return a Dhall list containing the
275+
objects, and use the `--documents` flag, e.g.:
276+
277+
```bash
278+
dhall-to-yaml --documents --omitEmpty <<< "let a = ./examples/deploymentSimple.dhall in [a, a]"
279+
```
280+
281+
If the objects are of different type, it's not possible to have separate documents in the same YAML file.
282+
However, since [k8s has a builtin `List` type for these cases](https://github.com/kubernetes/kubernetes/blob/master/hack/testdata/list.yaml),
283+
it's possible to use it together with the [union type of all k8s types that we generate][typesUnion].
284+
285+
So if we want to deploy e.g. a Deployment and a Service together, we can do:
286+
287+
```dhall
288+
let k8s = ./typesUnion.dhall
289+
290+
in
291+
{ apiVersion = "v1"
292+
, kind = "List"
293+
, items =
294+
[ k8s.Deployment ./my-deployment.dhall
295+
, k8s.Service ./my-service.dhall
296+
]
297+
}
298+
```
299+
300+
301+
## Projects Using `dhall-kubernetes`
302+
303+
* [dhall-prometheus-operator][dhall-prometheus-operator]: Provides types and default records for [Prometheus Operators][prometheus-operator].
304+
305+
306+
## Development
307+
308+
### Adding a new Kubernetes releases
309+
310+
To add a new supported release, run:
311+
312+
```bash
313+
./scripts/add-kubernetes-release.sh "${VERSION}"
314+
```
315+
316+
If you want to make a specific release the preferred release, run:
317+
318+
```
319+
$ echo "${VERSION}" > ./nix/preferred.txt
320+
$ ./scripts/generate.sh
321+
```
322+
323+
### Tests
324+
325+
All tests are defined in `release.nix`. We run these tests in CI in a [Hydra
326+
project][hydra-project].
327+
328+
You can run the tests locally with the following command:
329+
330+
```bash
331+
nix build --file ./release.nix
332+
```
333+
334+
### Generating `types` `default` and `README.md`
335+
336+
Running `scripts/generate.sh` will generate all dhall files from the kubernetes
337+
swagger specification, and copy them to `types` and `default`. It will also
338+
generate `README.md` from `docs/README.md.dhall`.
339+
340+
If you make changes to `scripts/convert.py` or `docs/README.md.dhall`, you need
341+
to run this command afterwards.
342+
343+
344+
[stack]: https://haskellstack.org/
345+
[hydra-project]: http://hydra.dhall-lang.org/project/dhall-kubernetes
346+
[dhall-lang]: https://github.com/dhall-lang/dhall-lang
347+
[dhall-website]: https://dhall-lang.org/
348+
[security-hashes]: https://github.com/dhall-lang/dhall-lang/wiki/Safety-guarantees#code-injection
349+
[typesUnion]: https://github.com/dhall-lang/dhall-kubernetes/blob/master/typesUnion.dhall
350+
[kubernetes]: https://kubernetes.io/
351+
[normalization]: https://en.wikipedia.org/wiki/Normalization_property_(abstract_rewriting)
352+
[nginx-ingress]: https://github.com/kubernetes/ingress-nginx
353+
[dhall-tutorial]: http://hackage.haskell.org/package/dhall-1.28.0/docs/Dhall-Tutorial.html
354+
[deployment]: ./schemas/io.k8s.api.apps.v1.Deployment.dhall
355+
[Ingress]: ./schemas/io.k8s.api.extensions.v1beta1.Ingress.dhall
356+
[prometheus-operator]: https://github.com/coreos/prometheus-operator
357+
[dhall-prometheus-operator]: https://github.com/coralogix/dhall-prometheus-operator

0 commit comments

Comments
 (0)