mirror of
https://github.com/dhall-lang/dhall-kubernetes.git
synced 2024-09-17 10:27:08 +03:00
Add high level API (#34)
This adds a high level API (+ tests) for: - Deployment.v1 - Service.v1
This commit is contained in:
parent
11a986ae11
commit
f8be1d55bc
349
README.md
349
README.md
@ -1,7 +1,30 @@
|
|||||||
# `dhall-kubernetes`
|
# `dhall-kubernetes`
|
||||||
|
|
||||||
Dhall bindings to Kubernetes.
|
`dhall-kubernetes` contains [Dhall][dhall-lang] bindings to [Kubernetes][kubernetes],
|
||||||
This will let you typecheck, template and modularize your Kubernetes definitions with [Dhall][dhall-lang].
|
so you can generate Kubernetes objects definitions from Dhall expressions.
|
||||||
|
This will let you easily typecheck, template and modularize your Kubernetes definitions.
|
||||||
|
|
||||||
|
## Why do I need this
|
||||||
|
|
||||||
|
Once you build a slightly non-trivial Kubernetes setup, with many objects floating
|
||||||
|
around, you'll encounter several issues:
|
||||||
|
1. Writing the definitions in YAML is really verbose, and the actually important
|
||||||
|
things don't stand out that much
|
||||||
|
2. Ok I have a bunch of objects that'll need to be configured together, how do I share data?
|
||||||
|
3. I'd like to reuse an object for different environments, but I cannot make it parametric..
|
||||||
|
4. In general, I'd really love to reuse parts of some definitions in other definitions
|
||||||
|
5. Oh no, I typoed a key and I had to wait until I pushed to the cluster to get an error back :(
|
||||||
|
|
||||||
|
The natural tendency is to reach for a templating language + a programming language to orchestrate that + some more configuration for it...
|
||||||
|
But this is just really messy (been there), and we can do better.
|
||||||
|
|
||||||
|
Dhall solves all of this, being a programming language with builtin templating,
|
||||||
|
all while being non-Turing complete, strongly typed and [strongly normalizing][normalization]
|
||||||
|
(i.e.: reduces everything to a normal form, no matter how much abstraction you build),
|
||||||
|
so saving you from the *"oh-noes-I-made-my-config-in-code-and-now-its-too-abstract"* nightmare.
|
||||||
|
|
||||||
|
For a Dhall Tutorial, see the [readme of the project][dhall-lang],
|
||||||
|
or the [full tutorial][dhall-tutorial].
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@ -14,105 +37,42 @@ stack install dhall dhall-json --resolver=nightly
|
|||||||
|
|
||||||
For a version compatible with a previous version, check out [this commit](https://github.com/dhall-lang/dhall-kubernetes/tree/b2357dcfa42a008efa203a850163d26f0d106e01).
|
For a version compatible with a previous version, check out [this commit](https://github.com/dhall-lang/dhall-kubernetes/tree/b2357dcfa42a008efa203a850163d26f0d106e01).
|
||||||
|
|
||||||
## Quick start
|
## Quickstart - main API
|
||||||
|
|
||||||
In the `types` folder you'll find the types for the Kubernetes definitions. E.g.
|
We provide a simple API for the most common cases (For a list, see the [api](./api) folder).
|
||||||
[here's][Deployment] the type for a Deployment.
|
|
||||||
|
|
||||||
Since _most_ of the fields in all definitions are optional, for better
|
Let's say we'd like to configure a Deployment exposing an `nginx` webserver.
|
||||||
ergonomics while coding Dhall we also generate default values for all types, in
|
In the following example, we:
|
||||||
the `default` folder. When some fields are required, the default value is a
|
1. Define a `config` for our service, by merging a [default config][default-deployment]
|
||||||
function whose input is a record of required fields, that returns the object
|
(with the Dhall record-merge operator `//`) with a record with our parameters.
|
||||||
with these fields set. E.g. the default for the Deployment is [this
|
2. In there we define the details of the Deployment we care about (note that we do the same
|
||||||
function][Deployment-default].
|
"merging with defaults" operation for our container as well, so we don't have to specify
|
||||||
|
all the parameters)
|
||||||
|
3. We call the [`mkDeployment`][mkDeployment] function on our `config`
|
||||||
|
|
||||||
Since this might sound a bit abstract, let's go with some examples. You can find
|
|
||||||
these examples in the [`./examples` folder](./examples) and evaluate them there.
|
|
||||||
|
|
||||||
### Example: Deployment
|
|
||||||
|
|
||||||
Let's say we have several services, whose configuration has this type:
|
|
||||||
```haskell
|
|
||||||
-- examples/Service.dhall
|
|
||||||
{ name : Text
|
|
||||||
, host : Text
|
|
||||||
, version : Text
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
So a configuration for a service might look like this:
|
|
||||||
```haskell
|
|
||||||
-- examples/service-foo.dhall
|
|
||||||
{ name = "foo"
|
|
||||||
, host = "foo.example.com"
|
|
||||||
, version = "1.0.1"
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
We can then make a Deployment object for this service:
|
|
||||||
```haskell
|
```haskell
|
||||||
-- examples/deployment.dhall
|
-- examples/deployment.dhall
|
||||||
-- Prelude imports
|
let config =
|
||||||
let map = https://raw.githubusercontent.com/dhall-lang/Prelude/e44284bc37a5808861dacd4c8bd13d18411cb961/List/map
|
../api/Deployment/default
|
||||||
in let Some = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/Some
|
//
|
||||||
in let None = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/None
|
{ name = "nginx"
|
||||||
|
, replicas = 2
|
||||||
-- import dhall-kubernetes types and defaults
|
, containers =
|
||||||
in let Deployment = ../types/io.k8s.api.apps.v1beta2.Deployment.dhall
|
[ ../api/Deployment/defaultContainer
|
||||||
in let Spec = ../types/io.k8s.api.apps.v1beta2.DeploymentSpec.dhall
|
//
|
||||||
in let PodSpec = ../types/io.k8s.api.core.v1.PodSpec.dhall
|
{ name = "nginx"
|
||||||
in let ContainerPort = ../types/io.k8s.api.core.v1.ContainerPort.dhall
|
, imageName = "nginx"
|
||||||
in let defaultDeployment = ../default/io.k8s.api.apps.v1beta2.Deployment.dhall
|
, imageTag = "1.15.3"
|
||||||
in let defaultMeta = ../default/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta.dhall
|
, port = [ 80 ] : Optional Natural
|
||||||
in let defaultSpec = ../default/io.k8s.api.apps.v1beta2.DeploymentSpec.dhall
|
}
|
||||||
in let defaultTemplate = ../default/io.k8s.api.core.v1.PodTemplateSpec.dhall
|
]
|
||||||
in let defaultPodSpec = ../default/io.k8s.api.core.v1.PodSpec.dhall
|
|
||||||
in let defaultSelector = ../default/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector.dhall
|
|
||||||
in let defaultContainer = ../default/io.k8s.api.core.v1.Container.dhall
|
|
||||||
in let defaultContainerPort = ../default/io.k8s.api.core.v1.ContainerPort.dhall
|
|
||||||
|
|
||||||
-- and our service
|
|
||||||
in let fooService = ./service-foo.dhall
|
|
||||||
|
|
||||||
-- Generate the DeploymentSpec for the service
|
|
||||||
in let selector = Some
|
|
||||||
(List { mapKey : Text, mapValue : Text })
|
|
||||||
[{ mapKey = "app", mapValue = fooService.name }]
|
|
||||||
|
|
||||||
in let spec = defaultSpec
|
|
||||||
{ selector = defaultSelector // { matchLabels = selector }
|
|
||||||
, template = defaultTemplate
|
|
||||||
{ metadata = defaultMeta
|
|
||||||
{ name = fooService.name } // { labels = selector }
|
|
||||||
} //
|
|
||||||
{ spec = Some PodSpec (defaultPodSpec
|
|
||||||
{ containers = [
|
|
||||||
defaultContainer
|
|
||||||
{ name = fooService.name } //
|
|
||||||
{ image = Some Text "your-container-service.io/${fooService.name}:${fooService.version}"
|
|
||||||
, imagePullPolicy = Some Text "Always"
|
|
||||||
, ports = Some
|
|
||||||
(List ContainerPort)
|
|
||||||
[(defaultContainerPort {containerPort = 8080})]
|
|
||||||
}
|
|
||||||
]})
|
|
||||||
}
|
}
|
||||||
} //
|
|
||||||
{ replicas = Some Natural 2
|
|
||||||
, revisionHistoryLimit = Some Natural 10
|
|
||||||
}
|
|
||||||
|
|
||||||
-- and here's the Deployment
|
in ../api/Deployment/mkDeployment config
|
||||||
in defaultDeployment
|
|
||||||
{ metadata = defaultMeta { name = fooService.name }
|
|
||||||
} //
|
|
||||||
{ spec = Some Spec spec } : Deployment
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
We convert it to yaml with:
|
We then run this through `dhall-to-yaml` to generate our Kubernetes definition:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dhall-to-yaml --omitNull < deployment.dhall
|
dhall-to-yaml --omitNull < deployment.dhall
|
||||||
@ -120,44 +80,96 @@ dhall-to-yaml --omitNull < deployment.dhall
|
|||||||
|
|
||||||
And we get:
|
And we get:
|
||||||
```yaml
|
```yaml
|
||||||
-- examples/out/deployment.yaml
|
## examples/out/deployment.yaml
|
||||||
apiVersion: apps/v1beta2
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
spec:
|
spec:
|
||||||
revisionHistoryLimit: 10
|
revisionHistoryLimit: 20
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: foo
|
app: nginx
|
||||||
|
strategy:
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 5
|
||||||
|
maxUnavailable: 0
|
||||||
|
type: RollingUpdate
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: your-container-service.io/foo:1.0.1
|
- image: nginx:1.15.3
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: foo
|
env: []
|
||||||
|
volumeMounts: []
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
name: nginx
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 80
|
||||||
|
volumes: []
|
||||||
metadata:
|
metadata:
|
||||||
name: foo
|
name: nginx
|
||||||
labels:
|
labels:
|
||||||
app: foo
|
app: nginx
|
||||||
replicas: 2
|
replicas: 2
|
||||||
metadata:
|
metadata:
|
||||||
name: foo
|
name: nginx
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Advanced usage - raw API
|
||||||
|
|
||||||
### Example: Ingress
|
If the main API is not enough (e.g. the object you'd like to generate is not in the list),
|
||||||
|
you can just fall back on using the raw Types and defaults the library provides
|
||||||
|
(and Pull Request here your program afterwards!).
|
||||||
|
|
||||||
|
Let's say we want to generate an Ingress definition (for an [Nginx Ingress][nginx-ingress])
|
||||||
|
that contains TLS certs and routes for every service.
|
||||||
|
For more examples of using this API see the [`./examples` folder](./examples).
|
||||||
|
|
||||||
|
In the [`types`](./types) folder you'll find the types for the Kubernetes definitions. E.g.
|
||||||
|
[here's][Ingress] the type for the Ingress.
|
||||||
|
|
||||||
|
Since _most_ of the fields in all definitions are optional, for better
|
||||||
|
ergonomics while coding Dhall we also generate default values for all types, in
|
||||||
|
the [`default`](./default) folder. When some fields are required, the default value
|
||||||
|
is a function whose input is a record of required fields, that returns the object
|
||||||
|
with these fields set. E.g. the default for the Ingress is [this
|
||||||
|
function][Ingress-default].
|
||||||
|
|
||||||
|
Let's say we have a Service with the following configuration:
|
||||||
|
|
||||||
Let's say we now want to generate an Ingress definition (for an Nginx Ingress)
|
|
||||||
that contains TLS certs and routes for every service. It would be something like
|
|
||||||
this:
|
|
||||||
```haskell
|
```haskell
|
||||||
-- examples/ingress.dhall
|
-- examples/myConfig.dhall
|
||||||
|
{ name = "foo"
|
||||||
|
, host = "foo.example.com"
|
||||||
|
, version = "1.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
That has the following type:
|
||||||
|
```haskell
|
||||||
|
-- examples/Config.dhall
|
||||||
|
{ name : Text
|
||||||
|
, host : Text
|
||||||
|
, version : Text
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
We can now expose this service out to the world with the Ingress:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
-- examples/ingressRaw.dhall
|
||||||
|
|
||||||
|
|
||||||
-- Prelude imports
|
-- Prelude imports
|
||||||
let map = https://raw.githubusercontent.com/dhall-lang/Prelude/e44284bc37a5808861dacd4c8bd13d18411cb961/List/map
|
let map = https://raw.githubusercontent.com/dhall-lang/Prelude/v2.0.0/List/map
|
||||||
in let Some = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/Some
|
in let Some = https://raw.githubusercontent.com/dhall-lang/Prelude/v2.0.0/Optional/Some
|
||||||
in let None = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/None
|
in let None = https://raw.githubusercontent.com/dhall-lang/Prelude/v2.0.0/Optional/None
|
||||||
|
|
||||||
-- dhall-kubernetes types and defaults
|
-- dhall-kubernetes types and defaults
|
||||||
in let TLS = ../types/io.k8s.api.extensions.v1beta1.IngressTLS.dhall
|
in let TLS = ../types/io.k8s.api.extensions.v1beta1.IngressTLS.dhall
|
||||||
@ -171,73 +183,82 @@ in let defaultSpec = ../default/io.k8s.api.extensions.v1beta1.IngressSpec.dha
|
|||||||
in let IntOrString = ../default/io.k8s.apimachinery.pkg.util.intstr.IntOrString.dhall
|
in let IntOrString = ../default/io.k8s.apimachinery.pkg.util.intstr.IntOrString.dhall
|
||||||
|
|
||||||
-- Our Service type
|
-- Our Service type
|
||||||
in let Service = ./Service.dhall
|
in let Service = ./Config.dhall
|
||||||
|
in let Config = { services : List Service }
|
||||||
|
|
||||||
-- Given a service, make a TLS definition with their host and certificate
|
-- A function to generate an ingress given a configuration
|
||||||
in let makeTLS = \(service : Service) ->
|
in let mkIngress : Config -> Ingress =
|
||||||
{ hosts = Some (List Text) [ service.host ]
|
|
||||||
, secretName = Some Text "${service.name}-certificate"
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Given a service, make an Ingress Rule
|
\(config : Config) ->
|
||||||
in let makeRule = \(service : Service) ->
|
|
||||||
{ host = Some Text service.host
|
|
||||||
, http = Some RuleVal
|
|
||||||
{ paths = [ { backend = { serviceName = service.name
|
|
||||||
, servicePort = IntOrString.Int 80
|
|
||||||
}
|
|
||||||
, path = None Text
|
|
||||||
}]}}
|
|
||||||
|
|
||||||
-- Nginx ingress requires a default service as a catchall
|
-- Given a service, make a TLS definition with their host and certificate
|
||||||
in let defaultService =
|
let makeTLS = \(service : Service) ->
|
||||||
{ name = "default"
|
{ hosts = Some (List Text) [ service.host ]
|
||||||
, host = "default.example.com"
|
, secretName = Some Text "${service.name}-certificate"
|
||||||
, version = " 1.0"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
-- List of services
|
-- Given a service, make an Ingress Rule
|
||||||
in let fooService = ./service-foo.dhall
|
in let makeRule = \(service : Service) ->
|
||||||
in let services =
|
{ host = Some Text service.host
|
||||||
[ fooService
|
, http = Some RuleVal
|
||||||
, defaultService
|
{ paths = [ { backend = { serviceName = service.name
|
||||||
]
|
, servicePort = IntOrString.Int 80
|
||||||
|
}
|
||||||
|
, path = None Text
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
-- Some metadata annotations
|
-- Nginx ingress requires a default service as a catchall
|
||||||
-- NOTE: `dhall-to-yaml` will generate a record with arbitrary keys from a list
|
in let defaultService =
|
||||||
-- of records where mapKey is the key and mapValue is the value of that key
|
{ name = "default"
|
||||||
in let genericRecord = List { mapKey : Text, mapValue : Text }
|
, host = "default.example.com"
|
||||||
in let kv = \(k : Text) -> \(v : Text) -> { mapKey = k, mapValue = v }
|
, version = " 1.0"
|
||||||
|
}
|
||||||
|
|
||||||
in let annotations = Some genericRecord
|
-- List of services
|
||||||
[ kv "kubernetes.io/ingress.class" "nginx"
|
in let services = config.services # [ defaultService ]
|
||||||
, kv "kubernetes.io/ingress.allow-http" "false"
|
|
||||||
]
|
|
||||||
|
|
||||||
-- Generate spec from services
|
-- Some metadata annotations
|
||||||
in let spec = defaultSpec //
|
-- NOTE: `dhall-to-yaml` will generate a record with arbitrary keys from a list
|
||||||
{ tls = Some (List TLS) (map Service TLS makeTLS services)
|
-- of records where mapKey is the key and mapValue is the value of that key
|
||||||
, rules = Some (List Rule) (map Service Rule makeRule services)
|
in let genericRecord = List { mapKey : Text, mapValue : Text }
|
||||||
}
|
in let kv = \(k : Text) -> \(v : Text) -> { mapKey = k, mapValue = v }
|
||||||
|
|
||||||
in defaultIngress
|
in let annotations = Some genericRecord
|
||||||
{ metadata = defaultMeta
|
[ kv "kubernetes.io/ingress.class" "nginx"
|
||||||
{ name = "nginx" } //
|
, kv "kubernetes.io/ingress.allow-http" "false"
|
||||||
{ annotations = annotations }
|
]
|
||||||
} //
|
|
||||||
{ spec = Some Spec spec } : Ingress
|
-- Generate spec from services
|
||||||
|
in let spec = defaultSpec //
|
||||||
|
{ tls = Some (List TLS) (map Service TLS makeTLS services)
|
||||||
|
, rules = Some (List Rule) (map Service Rule makeRule services)
|
||||||
|
}
|
||||||
|
|
||||||
|
in defaultIngress
|
||||||
|
{ metadata = defaultMeta
|
||||||
|
{ name = "nginx" } //
|
||||||
|
{ annotations = annotations }
|
||||||
|
} //
|
||||||
|
{ spec = Some Spec spec }
|
||||||
|
|
||||||
|
|
||||||
|
-- Here we import our example service, and generate the ingress with it
|
||||||
|
in mkIngress { services = [ ./myConfig.dhall ] }
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
As usual we get the yaml out by running:
|
As before we get the yaml out by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dhall-to-yaml --omitNull < ingress.yaml.dhall
|
dhall-to-yaml --omitNull < ingress.yaml.dhall
|
||||||
```
|
```
|
||||||
|
|
||||||
And we get:
|
Result:
|
||||||
```yaml
|
```yaml
|
||||||
-- examples/out/ingress.yaml
|
## examples/out/ingressRaw.yaml
|
||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
spec:
|
spec:
|
||||||
@ -294,5 +315,11 @@ to run `scripts/build-readme.sh`.
|
|||||||
|
|
||||||
[hydra-project]: http://hydra.dhall-lang.org/project/dhall-kubernetes
|
[hydra-project]: http://hydra.dhall-lang.org/project/dhall-kubernetes
|
||||||
[dhall-lang]: https://github.com/dhall-lang/dhall-lang
|
[dhall-lang]: https://github.com/dhall-lang/dhall-lang
|
||||||
[Deployment]: https://github.com/dhall-lang/dhall-kubernetes/blob/master/types/io.k8s.api.apps.v1beta2.Deployment.dhall
|
[kubernetes]: https://kubernetes.io/
|
||||||
[Deployment-default]: https://github.com/dhall-lang/dhall-kubernetes/blob/master/default/io.k8s.api.apps.v1beta2.Deployment.dhall
|
[normalization]: https://en.wikipedia.org/wiki/Normalization_property_(abstract_rewriting)
|
||||||
|
[nginx-ingress]: https://github.com/kubernetes/ingress-nginx
|
||||||
|
[dhall-tutorial]: http://hackage.haskell.org/package/dhall-1.17.0/docs/Dhall-Tutorial.html
|
||||||
|
[default-deployment]: ./api/Deployment/default
|
||||||
|
[mkDeployment]: ./api/Deployment/mkDeployment
|
||||||
|
[Ingress]: ./types/io.k8s.api.extensions.v1beta1.Ingress.dhall
|
||||||
|
[Ingress-default]: ./default/io.k8s.api.extensions.v1beta1.Ingress.dhall
|
||||||
|
13
api/Deployment/Container
Normal file
13
api/Deployment/Container
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{ name : Text
|
||||||
|
, imageName : Text
|
||||||
|
, imageTag : Text
|
||||||
|
, imagePullPolicy : Text
|
||||||
|
, minCPU : Natural
|
||||||
|
, maxCPU : Natural
|
||||||
|
, mounts : List ./Mount
|
||||||
|
, envVars : List { mapKey : Text, mapValue : Text }
|
||||||
|
, port : Optional Natural
|
||||||
|
, command : Optional (List Text)
|
||||||
|
, livenessProbe : Optional ./Probe
|
||||||
|
, readinessProbe : Optional ./Probe
|
||||||
|
}
|
10
api/Deployment/Deployment
Normal file
10
api/Deployment/Deployment
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{ name : Text
|
||||||
|
, replicas : Natural
|
||||||
|
, revisionHistoryLimit : Natural
|
||||||
|
, maxSurge : Natural
|
||||||
|
, maxUnavailable : Natural
|
||||||
|
, containers : List ./Container
|
||||||
|
, emptyVolumes : List { name : Text }
|
||||||
|
, secretVolumes : List { name : Text }
|
||||||
|
, pathVolumes : List { name : Text, path : Text }
|
||||||
|
}
|
4
api/Deployment/Mount
Normal file
4
api/Deployment/Mount
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{ mountPath : Text
|
||||||
|
, name : Text
|
||||||
|
, readOnly : Optional Bool
|
||||||
|
}
|
5
api/Deployment/Probe
Normal file
5
api/Deployment/Probe
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ initial : Natural
|
||||||
|
, period : Natural
|
||||||
|
, port : Natural
|
||||||
|
, path : Text
|
||||||
|
}
|
23
api/Deployment/RawDefaults
Normal file
23
api/Deployment/RawDefaults
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
let intOrString = ../../default/io.k8s.apimachinery.pkg.util.intstr.IntOrString.dhall
|
||||||
|
in
|
||||||
|
|
||||||
|
{ deployment = ../../default/io.k8s.api.apps.v1.Deployment.dhall
|
||||||
|
, container = ../../default/io.k8s.api.core.v1.Container.dhall
|
||||||
|
, containerPort = ../../default/io.k8s.api.core.v1.ContainerPort.dhall
|
||||||
|
, podSpec = ../../default/io.k8s.api.core.v1.PodSpec.dhall
|
||||||
|
, spec = ../../default/io.k8s.api.apps.v1.DeploymentSpec.dhall
|
||||||
|
, template = ../../default/io.k8s.api.core.v1.PodTemplateSpec.dhall
|
||||||
|
, probe = ../../default/io.k8s.api.core.v1.Probe.dhall
|
||||||
|
, httpGet = ../../default/io.k8s.api.core.v1.HTTPGetAction.dhall
|
||||||
|
, envVar = ../../default/io.k8s.api.core.v1.EnvVar.dhall
|
||||||
|
, mount = ../../default/io.k8s.api.core.v1.VolumeMount.dhall
|
||||||
|
, volume = ../../default/io.k8s.api.core.v1.Volume.dhall
|
||||||
|
, secretVolume = ../../default/io.k8s.api.core.v1.SecretVolumeSource.dhall
|
||||||
|
, emptyVolume = ../../default/io.k8s.api.core.v1.EmptyDirVolumeSource.dhall
|
||||||
|
, pathVolume = ../../default/io.k8s.api.core.v1.HostPathVolumeSource.dhall
|
||||||
|
, meta = ../../default/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta.dhall
|
||||||
|
, selector = ../../default/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector.dhall
|
||||||
|
, intOrString = intOrString
|
||||||
|
, Int = intOrString.Int
|
||||||
|
, String = intOrString.String
|
||||||
|
}
|
18
api/Deployment/RawTypes
Normal file
18
api/Deployment/RawTypes
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{ Deployment = ../../types/io.k8s.api.apps.v1.Deployment.dhall
|
||||||
|
, Container = ../../types/io.k8s.api.core.v1.Container.dhall
|
||||||
|
, ContainerPort = ../../types/io.k8s.api.core.v1.ContainerPort.dhall
|
||||||
|
, PodSpec = ../../types/io.k8s.api.core.v1.PodSpec.dhall
|
||||||
|
, RollingUpdate = ../../types/io.k8s.api.apps.v1.RollingUpdateDeployment.dhall
|
||||||
|
, Spec = ../../types/io.k8s.api.apps.v1.DeploymentSpec.dhall
|
||||||
|
, Strategy = ../../types/io.k8s.api.apps.v1.DeploymentStrategy.dhall
|
||||||
|
, Resources = ../../types/io.k8s.api.core.v1.ResourceRequirements.dhall
|
||||||
|
, Probe = ../../types/io.k8s.api.core.v1.Probe.dhall
|
||||||
|
, HttpGet = ../../types/io.k8s.api.core.v1.HTTPGetAction.dhall
|
||||||
|
, EnvVar = ../../types/io.k8s.api.core.v1.EnvVar.dhall
|
||||||
|
, Mount = ../../types/io.k8s.api.core.v1.VolumeMount.dhall
|
||||||
|
, Volume = ../../types/io.k8s.api.core.v1.Volume.dhall
|
||||||
|
, SecretVolume = ../../types/io.k8s.api.core.v1.SecretVolumeSource.dhall
|
||||||
|
, EmptyVolume = ../../types/io.k8s.api.core.v1.EmptyDirVolumeSource.dhall
|
||||||
|
, PathVolume = ../../types/io.k8s.api.core.v1.HostPathVolumeSource.dhall
|
||||||
|
, IntOrString = ../../types/io.k8s.apimachinery.pkg.util.intstr.IntOrString.dhall
|
||||||
|
}
|
10
api/Deployment/default
Normal file
10
api/Deployment/default
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{ name = "CHANGEME"
|
||||||
|
, replicas = 1
|
||||||
|
, revisionHistoryLimit = 20
|
||||||
|
, maxSurge = 5
|
||||||
|
, maxUnavailable = 0
|
||||||
|
, containers = [] : List ./Container
|
||||||
|
, emptyVolumes = [] : List { name : Text }
|
||||||
|
, secretVolumes = [] : List { name : Text }
|
||||||
|
, pathVolumes = [] : List { name : Text, path : Text }
|
||||||
|
} : ./Deployment
|
13
api/Deployment/defaultContainer
Normal file
13
api/Deployment/defaultContainer
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{ name = "CHANGEME"
|
||||||
|
, imageName = "SOME_IMAGE"
|
||||||
|
, imageTag = "0.1"
|
||||||
|
, imagePullPolicy = "Always"
|
||||||
|
, minCPU = 10
|
||||||
|
, maxCPU = 500
|
||||||
|
, mounts = [] : List ./Mount
|
||||||
|
, envVars = [] : List { mapKey : Text, mapValue : Text }
|
||||||
|
, port = [] : Optional Natural
|
||||||
|
, command = [] : Optional (List Text)
|
||||||
|
, livenessProbe = [] : Optional ./Probe
|
||||||
|
, readinessProbe = [] : Optional ./Probe
|
||||||
|
} : ./Container
|
170
api/Deployment/mkDeployment
Normal file
170
api/Deployment/mkDeployment
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
-- Prelude
|
||||||
|
let Prelude = https://raw.githubusercontent.com/dhall-lang/Prelude/v2.0.0/package.dhall
|
||||||
|
in let map = Prelude.`List`.map
|
||||||
|
in let Some = Prelude.`Optional`.Some
|
||||||
|
in let None = Prelude.`Optional`.None
|
||||||
|
in let kv = Prelude.JSON.keyText
|
||||||
|
|
||||||
|
-- Kubernetes types and defaults
|
||||||
|
in let Types = ./RawTypes
|
||||||
|
in let default = ./RawDefaults
|
||||||
|
|
||||||
|
-- Types for dynamic records
|
||||||
|
in let KV = { mapKey : Text, mapValue : Text }
|
||||||
|
in let ListKV = List KV
|
||||||
|
|
||||||
|
|
||||||
|
in let mkProbe : ./Probe → Optional Types.Probe =
|
||||||
|
|
||||||
|
λ(probe : ./Probe) →
|
||||||
|
|
||||||
|
Some Types.Probe
|
||||||
|
(default.probe //
|
||||||
|
{ initialDelaySeconds = Some Natural probe.initial
|
||||||
|
, periodSeconds = Some Natural probe.period
|
||||||
|
, httpGet = Some Types.HttpGet
|
||||||
|
(default.httpGet
|
||||||
|
{ port = default.Int probe.port } //
|
||||||
|
{ path = Some Text probe.path
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
in let mkEnvVar : KV → Types.EnvVar =
|
||||||
|
|
||||||
|
λ(var : KV) →
|
||||||
|
|
||||||
|
default.envVar
|
||||||
|
{ name = var.mapKey } //
|
||||||
|
{ value = Some Text var.mapValue }
|
||||||
|
|
||||||
|
|
||||||
|
in let mkEmptyVolume : { name : Text } → Types.Volume =
|
||||||
|
|
||||||
|
λ(vol : { name : Text }) →
|
||||||
|
|
||||||
|
default.volume
|
||||||
|
{ name = vol.name } //
|
||||||
|
{ emptyDir = Some Types.EmptyVolume default.emptyVolume }
|
||||||
|
|
||||||
|
|
||||||
|
in let mkSecretVolume : { name : Text } → Types.Volume =
|
||||||
|
|
||||||
|
λ(vol : { name : Text }) →
|
||||||
|
|
||||||
|
default.volume
|
||||||
|
{ name = vol.name } //
|
||||||
|
{ secret = Some Types.SecretVolume
|
||||||
|
(default.secretVolume // { secretName = Some Text vol.name } )
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
in let mkPathVolume : { name : Text, path : Text } → Types.Volume =
|
||||||
|
|
||||||
|
λ(vol : { name : Text, path : Text }) →
|
||||||
|
|
||||||
|
default.volume
|
||||||
|
{ name = vol.name } //
|
||||||
|
{ hostPath = Some Types.PathVolume
|
||||||
|
(default.pathVolume { path = vol.path })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
in let mkMount : ./Mount → Types.Mount =
|
||||||
|
|
||||||
|
λ(mount : ./Mount) →
|
||||||
|
|
||||||
|
default.mount
|
||||||
|
{ mountPath = mount.mountPath
|
||||||
|
, name = mount.name
|
||||||
|
} //
|
||||||
|
{ readOnly = mount.readOnly }
|
||||||
|
|
||||||
|
|
||||||
|
in let mkContainer : ./Container → Types.Container =
|
||||||
|
|
||||||
|
λ(container : ./Container) →
|
||||||
|
|
||||||
|
default.container
|
||||||
|
{ name = container.name } //
|
||||||
|
{ image = Some Text "${container.imageName}:${container.imageTag}"
|
||||||
|
, imagePullPolicy = Some Text container.imagePullPolicy
|
||||||
|
, ports = Optional/fold
|
||||||
|
Natural
|
||||||
|
container.port
|
||||||
|
(Optional (List Types.ContainerPort))
|
||||||
|
(λ(port : Natural) → Some (List Types.ContainerPort)
|
||||||
|
[(default.containerPort { containerPort = port })])
|
||||||
|
(None (List Types.ContainerPort))
|
||||||
|
, resources = Some Types.Resources
|
||||||
|
{ limits = Some ListKV [kv "cpu" "${Natural/show container.maxCPU}m"]
|
||||||
|
, requests = Some ListKV [kv "cpu" "${Natural/show container.minCPU}m"]
|
||||||
|
}
|
||||||
|
, command = container.command
|
||||||
|
, volumeMounts = Some (List Types.Mount)
|
||||||
|
(map ./Mount Types.Mount mkMount container.mounts)
|
||||||
|
-- Poll the container to see if the it's alive or we should restart it
|
||||||
|
, livenessProbe = Optional/fold
|
||||||
|
./Probe
|
||||||
|
container.livenessProbe
|
||||||
|
(Optional Types.Probe)
|
||||||
|
mkProbe
|
||||||
|
(None Types.Probe)
|
||||||
|
-- Poll the container to see that it's ready for requests
|
||||||
|
, readinessProbe = Optional/fold
|
||||||
|
./Probe
|
||||||
|
container.readinessProbe
|
||||||
|
(Optional Types.Probe)
|
||||||
|
mkProbe
|
||||||
|
(None Types.Probe)
|
||||||
|
, env = Some (List Types.EnvVar)
|
||||||
|
(map { mapKey : Text , mapValue : Text } Types.EnvVar mkEnvVar container.envVars)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
in let mkDeployment : ./Deployment → Types.Deployment =
|
||||||
|
|
||||||
|
λ(deployment : ./Deployment) →
|
||||||
|
|
||||||
|
let selector = Some ListKV [kv "app" deployment.name]
|
||||||
|
|
||||||
|
in let emptyVolumes = map { name : Text } Types.Volume mkEmptyVolume deployment.emptyVolumes
|
||||||
|
in let secretVolumes = map { name : Text } Types.Volume mkSecretVolume deployment.secretVolumes
|
||||||
|
in let pathVolumes = map { name : Text, path : Text } Types.Volume mkPathVolume deployment.pathVolumes
|
||||||
|
in let volumes = Some (List Types.Volume) (emptyVolumes # secretVolumes # pathVolumes)
|
||||||
|
|
||||||
|
in let spec = default.spec
|
||||||
|
{ selector = default.selector // { matchLabels = selector }
|
||||||
|
, template = default.template
|
||||||
|
{ metadata = default.meta
|
||||||
|
{ name = deployment.name } // { labels = selector }
|
||||||
|
} //
|
||||||
|
{ spec = Some Types.PodSpec (default.podSpec
|
||||||
|
{ containers = map ./Container Types.Container mkContainer deployment.containers
|
||||||
|
} //
|
||||||
|
{ volumes = volumes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} //
|
||||||
|
{ replicas = Some Natural deployment.replicas
|
||||||
|
-- Don't keep all the ReplicaSets
|
||||||
|
, revisionHistoryLimit = Some Natural deployment.revisionHistoryLimit
|
||||||
|
, strategy = Some Types.Strategy
|
||||||
|
-- Control the RollingUpdate so the app is always available. For more info see:
|
||||||
|
-- https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
|
||||||
|
{ type = Some Text "RollingUpdate"
|
||||||
|
, rollingUpdate = Some Types.RollingUpdate
|
||||||
|
{ maxSurge = Some Types.IntOrString (default.Int deployment.maxSurge)
|
||||||
|
, maxUnavailable = Some Types.IntOrString (default.Int deployment.maxUnavailable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
in default.deployment
|
||||||
|
{ metadata = default.meta { name = deployment.name }
|
||||||
|
} //
|
||||||
|
{ spec = Some Types.Spec spec
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
in mkDeployment
|
9
api/Service/RawDefaults
Normal file
9
api/Service/RawDefaults
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
let intOrString = ../../default/io.k8s.apimachinery.pkg.util.intstr.IntOrString.dhall
|
||||||
|
in
|
||||||
|
{ service = ../../default/io.k8s.api.core.v1.Service.dhall
|
||||||
|
, spec = ../../default/io.k8s.api.core.v1.ServiceSpec.dhall
|
||||||
|
, port = ../../default/io.k8s.api.core.v1.ServicePort.dhall
|
||||||
|
, meta = ../../default/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta.dhall
|
||||||
|
, Int = intOrString.Int
|
||||||
|
, String = intOrString.String
|
||||||
|
}
|
5
api/Service/RawTypes
Normal file
5
api/Service/RawTypes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ Service = ../../types/io.k8s.api.core.v1.Service.dhall
|
||||||
|
, Spec = ../../types/io.k8s.api.core.v1.ServiceSpec.dhall
|
||||||
|
, Port = ../../types/io.k8s.api.core.v1.ServicePort.dhall
|
||||||
|
, IntOrString = ../../types/io.k8s.apimachinery.pkg.util.intstr.IntOrString.dhall
|
||||||
|
}
|
6
api/Service/Service
Normal file
6
api/Service/Service
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{ name : Text
|
||||||
|
, annotations : List { mapKey : Text, mapValue : Text }
|
||||||
|
, containerPort : Natural
|
||||||
|
, outPort : Natural
|
||||||
|
, type : ./ServiceType
|
||||||
|
}
|
5
api/Service/ServiceType
Normal file
5
api/Service/ServiceType
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
< ClusterIP : {}
|
||||||
|
| NodePort : {}
|
||||||
|
| LoadBalancer : {}
|
||||||
|
| ExternalName : {}
|
||||||
|
>
|
6
api/Service/default
Normal file
6
api/Service/default
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{ name = "CHANGEME"
|
||||||
|
, annotations = [] : List { mapKey : Text, mapValue : Text }
|
||||||
|
, containerPort = 8080
|
||||||
|
, outPort = 80
|
||||||
|
, type = (constructors ./ServiceType).NodePort {=}
|
||||||
|
}
|
51
api/Service/mkService
Normal file
51
api/Service/mkService
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
-- Prelude
|
||||||
|
let Prelude = https://raw.githubusercontent.com/dhall-lang/Prelude/v2.0.0/package.dhall
|
||||||
|
in let Some = Prelude.`Optional`.Some
|
||||||
|
in let kv = Prelude.JSON.keyText
|
||||||
|
|
||||||
|
-- Kubernetes types and defaults
|
||||||
|
in let Types = ./RawTypes
|
||||||
|
in let default = ./RawDefaults
|
||||||
|
|
||||||
|
-- Types for dynamic records
|
||||||
|
in let KV = { mapKey : Text, mapValue : Text }
|
||||||
|
in let ListKV = List KV
|
||||||
|
|
||||||
|
|
||||||
|
in let mkService : ./Service → Types.Service =
|
||||||
|
|
||||||
|
λ(service : ./Service) →
|
||||||
|
|
||||||
|
let selector = Some ListKV [kv "app" service.name]
|
||||||
|
|
||||||
|
in let meta = default.meta
|
||||||
|
{ name = service.name } //
|
||||||
|
{ labels = selector
|
||||||
|
, annotations = Some ListKV service.annotations
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Handlers for the ServiceType union
|
||||||
|
in let handlers =
|
||||||
|
{ ClusterIP = \(_ : {}) -> "ClusterIP"
|
||||||
|
, NodePort = \(_ : {}) -> "NodePort"
|
||||||
|
, LoadBalancer = \(_ : {}) -> "LoadBalancer"
|
||||||
|
, ExternalName = \(_ : {}) -> "ExternalName"
|
||||||
|
}
|
||||||
|
|
||||||
|
in let spec = default.spec //
|
||||||
|
{ type = Some Text (merge handlers service.type : Text)
|
||||||
|
, ports = Some (List Types.Port)
|
||||||
|
[ default.port
|
||||||
|
{ port = service.outPort } //
|
||||||
|
{ targetPort = Some Types.IntOrString (default.Int service.containerPort) }
|
||||||
|
]
|
||||||
|
, selector = selector
|
||||||
|
}
|
||||||
|
|
||||||
|
in default.service
|
||||||
|
{ metadata = meta
|
||||||
|
} //
|
||||||
|
{ spec = Some Types.Spec spec
|
||||||
|
} : Types.Service
|
||||||
|
|
||||||
|
in mkService
|
@ -1,8 +1,31 @@
|
|||||||
''
|
''
|
||||||
# `dhall-kubernetes`
|
# `dhall-kubernetes`
|
||||||
|
|
||||||
Dhall bindings to Kubernetes.
|
`dhall-kubernetes` contains [Dhall][dhall-lang] bindings to [Kubernetes][kubernetes],
|
||||||
This will let you typecheck, template and modularize your Kubernetes definitions with [Dhall][dhall-lang].
|
so you can generate Kubernetes objects definitions from Dhall expressions.
|
||||||
|
This will let you easily typecheck, template and modularize your Kubernetes definitions.
|
||||||
|
|
||||||
|
## Why do I need this
|
||||||
|
|
||||||
|
Once you build a slightly non-trivial Kubernetes setup, with many objects floating
|
||||||
|
around, you'll encounter several issues:
|
||||||
|
1. Writing the definitions in YAML is really verbose, and the actually important
|
||||||
|
things don't stand out that much
|
||||||
|
2. Ok I have a bunch of objects that'll need to be configured together, how do I share data?
|
||||||
|
3. I'd like to reuse an object for different environments, but I cannot make it parametric..
|
||||||
|
4. In general, I'd really love to reuse parts of some definitions in other definitions
|
||||||
|
5. Oh no, I typoed a key and I had to wait until I pushed to the cluster to get an error back :(
|
||||||
|
|
||||||
|
The natural tendency is to reach for a templating language + a programming language to orchestrate that + some more configuration for it...
|
||||||
|
But this is just really messy (been there), and we can do better.
|
||||||
|
|
||||||
|
Dhall solves all of this, being a programming language with builtin templating,
|
||||||
|
all while being non-Turing complete, strongly typed and [strongly normalizing][normalization]
|
||||||
|
(i.e.: reduces everything to a normal form, no matter how much abstraction you build),
|
||||||
|
so saving you from the *"oh-noes-I-made-my-config-in-code-and-now-its-too-abstract"* nightmare.
|
||||||
|
|
||||||
|
For a Dhall Tutorial, see the [readme of the project][dhall-lang],
|
||||||
|
or the [full tutorial][dhall-tutorial].
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@ -15,42 +38,25 @@ stack install dhall dhall-json --resolver=nightly
|
|||||||
|
|
||||||
For a version compatible with a previous version, check out [this commit](https://github.com/dhall-lang/dhall-kubernetes/tree/b2357dcfa42a008efa203a850163d26f0d106e01).
|
For a version compatible with a previous version, check out [this commit](https://github.com/dhall-lang/dhall-kubernetes/tree/b2357dcfa42a008efa203a850163d26f0d106e01).
|
||||||
|
|
||||||
## Quick start
|
## Quickstart - main API
|
||||||
|
|
||||||
In the `types` folder you'll find the types for the Kubernetes definitions. E.g.
|
We provide a simple API for the most common cases (For a list, see the [api](./api) folder).
|
||||||
[here's][Deployment] the type for a Deployment.
|
|
||||||
|
|
||||||
Since _most_ of the fields in all definitions are optional, for better
|
Let's say we'd like to configure a Deployment exposing an `nginx` webserver.
|
||||||
ergonomics while coding Dhall we also generate default values for all types, in
|
In the following example, we:
|
||||||
the `default` folder. When some fields are required, the default value is a
|
1. Define a `config` for our service, by merging a [default config][default-deployment]
|
||||||
function whose input is a record of required fields, that returns the object
|
(with the Dhall record-merge operator `//`) with a record with our parameters.
|
||||||
with these fields set. E.g. the default for the Deployment is [this
|
2. In there we define the details of the Deployment we care about (note that we do the same
|
||||||
function][Deployment-default].
|
"merging with defaults" operation for our container as well, so we don't have to specify
|
||||||
|
all the parameters)
|
||||||
|
3. We call the [`mkDeployment`][mkDeployment] function on our `config`
|
||||||
|
|
||||||
Since this might sound a bit abstract, let's go with some examples. You can find
|
|
||||||
these examples in the [`./examples` folder](./examples) and evaluate them there.
|
|
||||||
|
|
||||||
### Example: Deployment
|
|
||||||
|
|
||||||
Let's say we have several services, whose configuration has this type:
|
|
||||||
```haskell
|
|
||||||
-- examples/Service.dhall
|
|
||||||
${../examples/Service.dhall as Text}
|
|
||||||
```
|
|
||||||
|
|
||||||
So a configuration for a service might look like this:
|
|
||||||
```haskell
|
|
||||||
-- examples/service-foo.dhall
|
|
||||||
${../examples/service-foo.dhall as Text}
|
|
||||||
```
|
|
||||||
|
|
||||||
We can then make a Deployment object for this service:
|
|
||||||
```haskell
|
```haskell
|
||||||
-- examples/deployment.dhall
|
-- examples/deployment.dhall
|
||||||
${../examples/deployment.dhall as Text}
|
${../examples/deployment.dhall as Text}
|
||||||
```
|
```
|
||||||
|
|
||||||
We convert it to yaml with:
|
We then run this through `dhall-to-yaml` to generate our Kubernetes definition:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dhall-to-yaml --omitNull < deployment.dhall
|
dhall-to-yaml --omitNull < deployment.dhall
|
||||||
@ -58,31 +64,62 @@ dhall-to-yaml --omitNull < deployment.dhall
|
|||||||
|
|
||||||
And we get:
|
And we get:
|
||||||
```yaml
|
```yaml
|
||||||
-- examples/out/deployment.yaml
|
## examples/out/deployment.yaml
|
||||||
${../examples/out/deployment.yaml as Text}
|
${../examples/out/deployment.yaml as Text}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Advanced usage - raw API
|
||||||
|
|
||||||
### Example: Ingress
|
If the main API is not enough (e.g. the object you'd like to generate is not in the list),
|
||||||
|
you can just fall back on using the raw Types and defaults the library provides
|
||||||
|
(and Pull Request here your program afterwards!).
|
||||||
|
|
||||||
|
Let's say we want to generate an Ingress definition (for an [Nginx Ingress][nginx-ingress])
|
||||||
|
that contains TLS certs and routes for every service.
|
||||||
|
For more examples of using this API see the [`./examples` folder](./examples).
|
||||||
|
|
||||||
|
In the [`types`](./types) folder you'll find the types for the Kubernetes definitions. E.g.
|
||||||
|
[here's][Ingress] the type for the Ingress.
|
||||||
|
|
||||||
|
Since _most_ of the fields in all definitions are optional, for better
|
||||||
|
ergonomics while coding Dhall we also generate default values for all types, in
|
||||||
|
the [`default`](./default) folder. When some fields are required, the default value
|
||||||
|
is a function whose input is a record of required fields, that returns the object
|
||||||
|
with these fields set. E.g. the default for the Ingress is [this
|
||||||
|
function][Ingress-default].
|
||||||
|
|
||||||
|
Let's say we have a Service with the following configuration:
|
||||||
|
|
||||||
Let's say we now want to generate an Ingress definition (for an Nginx Ingress)
|
|
||||||
that contains TLS certs and routes for every service. It would be something like
|
|
||||||
this:
|
|
||||||
```haskell
|
```haskell
|
||||||
-- examples/ingress.dhall
|
-- examples/myConfig.dhall
|
||||||
${../examples/ingress.dhall as Text}
|
${../examples/myConfig.dhall as Text}
|
||||||
```
|
```
|
||||||
|
|
||||||
As usual we get the yaml out by running:
|
That has the following type:
|
||||||
|
```haskell
|
||||||
|
-- examples/Config.dhall
|
||||||
|
${../examples/Config.dhall as Text}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can now expose this service out to the world with the Ingress:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
-- examples/ingressRaw.dhall
|
||||||
|
|
||||||
|
|
||||||
|
${../examples/ingressRaw.dhall as Text}
|
||||||
|
```
|
||||||
|
|
||||||
|
As before we get the yaml out by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dhall-to-yaml --omitNull < ingress.yaml.dhall
|
dhall-to-yaml --omitNull < ingress.yaml.dhall
|
||||||
```
|
```
|
||||||
|
|
||||||
And we get:
|
Result:
|
||||||
```yaml
|
```yaml
|
||||||
-- examples/out/ingress.yaml
|
## examples/out/ingressRaw.yaml
|
||||||
${../examples/out/ingress.yaml as Text}
|
${../examples/out/ingressRaw.yaml as Text}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
@ -110,6 +147,12 @@ to run `scripts/build-readme.sh`.
|
|||||||
|
|
||||||
[hydra-project]: http://hydra.dhall-lang.org/project/dhall-kubernetes
|
[hydra-project]: http://hydra.dhall-lang.org/project/dhall-kubernetes
|
||||||
[dhall-lang]: https://github.com/dhall-lang/dhall-lang
|
[dhall-lang]: https://github.com/dhall-lang/dhall-lang
|
||||||
[Deployment]: https://github.com/dhall-lang/dhall-kubernetes/blob/master/types/io.k8s.api.apps.v1beta2.Deployment.dhall
|
[kubernetes]: https://kubernetes.io/
|
||||||
[Deployment-default]: https://github.com/dhall-lang/dhall-kubernetes/blob/master/default/io.k8s.api.apps.v1beta2.Deployment.dhall
|
[normalization]: https://en.wikipedia.org/wiki/Normalization_property_(abstract_rewriting)
|
||||||
|
[nginx-ingress]: https://github.com/kubernetes/ingress-nginx
|
||||||
|
[dhall-tutorial]: http://hackage.haskell.org/package/dhall-1.17.0/docs/Dhall-Tutorial.html
|
||||||
|
[default-deployment]: ./api/Deployment/default
|
||||||
|
[mkDeployment]: ./api/Deployment/mkDeployment
|
||||||
|
[Ingress]: ./types/io.k8s.api.extensions.v1beta1.Ingress.dhall
|
||||||
|
[Ingress-default]: ./default/io.k8s.api.extensions.v1beta1.Ingress.dhall
|
||||||
''
|
''
|
||||||
|
@ -1,55 +1,17 @@
|
|||||||
-- Prelude imports
|
let config =
|
||||||
let map = https://raw.githubusercontent.com/dhall-lang/Prelude/e44284bc37a5808861dacd4c8bd13d18411cb961/List/map
|
../api/Deployment/default
|
||||||
in let Some = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/Some
|
//
|
||||||
in let None = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/None
|
{ name = "nginx"
|
||||||
|
, replicas = 2
|
||||||
-- import dhall-kubernetes types and defaults
|
, containers =
|
||||||
in let Deployment = ../types/io.k8s.api.apps.v1beta2.Deployment.dhall
|
[ ../api/Deployment/defaultContainer
|
||||||
in let Spec = ../types/io.k8s.api.apps.v1beta2.DeploymentSpec.dhall
|
//
|
||||||
in let PodSpec = ../types/io.k8s.api.core.v1.PodSpec.dhall
|
{ name = "nginx"
|
||||||
in let ContainerPort = ../types/io.k8s.api.core.v1.ContainerPort.dhall
|
, imageName = "nginx"
|
||||||
in let defaultDeployment = ../default/io.k8s.api.apps.v1beta2.Deployment.dhall
|
, imageTag = "1.15.3"
|
||||||
in let defaultMeta = ../default/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta.dhall
|
, port = [ 80 ] : Optional Natural
|
||||||
in let defaultSpec = ../default/io.k8s.api.apps.v1beta2.DeploymentSpec.dhall
|
}
|
||||||
in let defaultTemplate = ../default/io.k8s.api.core.v1.PodTemplateSpec.dhall
|
]
|
||||||
in let defaultPodSpec = ../default/io.k8s.api.core.v1.PodSpec.dhall
|
|
||||||
in let defaultSelector = ../default/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector.dhall
|
|
||||||
in let defaultContainer = ../default/io.k8s.api.core.v1.Container.dhall
|
|
||||||
in let defaultContainerPort = ../default/io.k8s.api.core.v1.ContainerPort.dhall
|
|
||||||
|
|
||||||
-- and our service
|
|
||||||
in let fooService = ./service-foo.dhall
|
|
||||||
|
|
||||||
-- Generate the DeploymentSpec for the service
|
|
||||||
in let selector = Some
|
|
||||||
(List { mapKey : Text, mapValue : Text })
|
|
||||||
[{ mapKey = "app", mapValue = fooService.name }]
|
|
||||||
|
|
||||||
in let spec = defaultSpec
|
|
||||||
{ selector = defaultSelector // { matchLabels = selector }
|
|
||||||
, template = defaultTemplate
|
|
||||||
{ metadata = defaultMeta
|
|
||||||
{ name = fooService.name } // { labels = selector }
|
|
||||||
} //
|
|
||||||
{ spec = Some PodSpec (defaultPodSpec
|
|
||||||
{ containers = [
|
|
||||||
defaultContainer
|
|
||||||
{ name = fooService.name } //
|
|
||||||
{ image = Some Text "your-container-service.io/${fooService.name}:${fooService.version}"
|
|
||||||
, imagePullPolicy = Some Text "Always"
|
|
||||||
, ports = Some
|
|
||||||
(List ContainerPort)
|
|
||||||
[(defaultContainerPort {containerPort = 8080})]
|
|
||||||
}
|
|
||||||
]})
|
|
||||||
}
|
}
|
||||||
} //
|
|
||||||
{ replicas = Some Natural 2
|
|
||||||
, revisionHistoryLimit = Some Natural 10
|
|
||||||
}
|
|
||||||
|
|
||||||
-- and here's the Deployment
|
in ../api/Deployment/mkDeployment config
|
||||||
in defaultDeployment
|
|
||||||
{ metadata = defaultMeta { name = fooService.name }
|
|
||||||
} //
|
|
||||||
{ spec = Some Spec spec } : Deployment
|
|
||||||
|
80
examples/deploymentRaw.dhall
Normal file
80
examples/deploymentRaw.dhall
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
-- Prelude imports
|
||||||
|
let map = https://raw.githubusercontent.com/dhall-lang/Prelude/e44284bc37a5808861dacd4c8bd13d18411cb961/List/map
|
||||||
|
in let Some = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/Some
|
||||||
|
in let None = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/None
|
||||||
|
|
||||||
|
-- import dhall-kubernetes types and defaults
|
||||||
|
in let Deployment = ../types/io.k8s.api.apps.v1beta2.Deployment.dhall
|
||||||
|
in let Spec = ../types/io.k8s.api.apps.v1beta2.DeploymentSpec.dhall
|
||||||
|
in let PodSpec = ../types/io.k8s.api.core.v1.PodSpec.dhall
|
||||||
|
in let ContainerPort = ../types/io.k8s.api.core.v1.ContainerPort.dhall
|
||||||
|
in let defaultDeployment = ../default/io.k8s.api.apps.v1beta2.Deployment.dhall
|
||||||
|
in let defaultMeta = ../default/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta.dhall
|
||||||
|
in let defaultSpec = ../default/io.k8s.api.apps.v1beta2.DeploymentSpec.dhall
|
||||||
|
in let defaultTemplate = ../default/io.k8s.api.core.v1.PodTemplateSpec.dhall
|
||||||
|
in let defaultPodSpec = ../default/io.k8s.api.core.v1.PodSpec.dhall
|
||||||
|
in let defaultSelector = ../default/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector.dhall
|
||||||
|
in let defaultContainer = ../default/io.k8s.api.core.v1.Container.dhall
|
||||||
|
in let defaultContainerPort = ../default/io.k8s.api.core.v1.ContainerPort.dhall
|
||||||
|
|
||||||
|
{-
|
||||||
|
|
||||||
|
Here we import the Config type.
|
||||||
|
It's going to be the input to our mkDeployment function,
|
||||||
|
and contains the configuration for the Deployment.
|
||||||
|
|
||||||
|
-}
|
||||||
|
in let Config = ./Config.dhall
|
||||||
|
|
||||||
|
|
||||||
|
-- So here we define a function that outputs a Deployment
|
||||||
|
in let mkDeployment : Config -> Deployment =
|
||||||
|
|
||||||
|
\(deployment : Config) ->
|
||||||
|
|
||||||
|
let selector = Some (List { mapKey : Text, mapValue : Text })
|
||||||
|
[{ mapKey = "app", mapValue = deployment.name }]
|
||||||
|
|
||||||
|
in let spec = defaultSpec
|
||||||
|
{ selector = defaultSelector // { matchLabels = selector }
|
||||||
|
, template = defaultTemplate
|
||||||
|
{ metadata = defaultMeta
|
||||||
|
{ name = deployment.name } // { labels = selector }
|
||||||
|
} //
|
||||||
|
{ spec = Some PodSpec (defaultPodSpec
|
||||||
|
{ containers = [
|
||||||
|
defaultContainer
|
||||||
|
{ name = deployment.name } //
|
||||||
|
{ image = Some Text "your-container-service.io/${deployment.name}:${deployment.version}"
|
||||||
|
, imagePullPolicy = Some Text "Always"
|
||||||
|
, ports = Some (List ContainerPort)
|
||||||
|
[(defaultContainerPort {containerPort = 8080})]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} //
|
||||||
|
{ replicas = Some Natural 2
|
||||||
|
, revisionHistoryLimit = Some Natural 10
|
||||||
|
}
|
||||||
|
|
||||||
|
in defaultDeployment
|
||||||
|
{ metadata = defaultMeta { name = deployment.name }
|
||||||
|
} //
|
||||||
|
{ spec = Some Spec spec } : Deployment
|
||||||
|
|
||||||
|
|
||||||
|
{-
|
||||||
|
|
||||||
|
..and to keep the example self contained we import our config here.
|
||||||
|
A more modular approach would be to just define a function to make
|
||||||
|
the Deployment in this file, and then apply the right configuration
|
||||||
|
at the command line or in another Dhall file.
|
||||||
|
|
||||||
|
E.g.: `dhall-to-yaml --omitNull <<< "./examples/deploymentRaw.dhall ./myConfig.dhall"`
|
||||||
|
|
||||||
|
-}
|
||||||
|
in let myConfig = ./myConfig.dhall
|
||||||
|
|
||||||
|
-- and here we apply the deployment-making function to our config
|
||||||
|
in mkDeployment myConfig
|
@ -1,72 +0,0 @@
|
|||||||
-- Prelude imports
|
|
||||||
let map = https://raw.githubusercontent.com/dhall-lang/Prelude/e44284bc37a5808861dacd4c8bd13d18411cb961/List/map
|
|
||||||
in let Some = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/Some
|
|
||||||
in let None = https://raw.githubusercontent.com/dhall-lang/Prelude/c79c2bc3c46f129cc5b6d594ce298a381bcae92c/Optional/None
|
|
||||||
|
|
||||||
-- dhall-kubernetes types and defaults
|
|
||||||
in let TLS = ../types/io.k8s.api.extensions.v1beta1.IngressTLS.dhall
|
|
||||||
in let Rule = ../types/io.k8s.api.extensions.v1beta1.IngressRule.dhall
|
|
||||||
in let RuleVal = ../types/io.k8s.api.extensions.v1beta1.HTTPIngressRuleValue.dhall
|
|
||||||
in let Spec = ../types/io.k8s.api.extensions.v1beta1.IngressSpec.dhall
|
|
||||||
in let Ingress = ../types/io.k8s.api.extensions.v1beta1.Ingress.dhall
|
|
||||||
in let defaultIngress = ../default/io.k8s.api.extensions.v1beta1.Ingress.dhall
|
|
||||||
in let defaultMeta = ../default/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta.dhall
|
|
||||||
in let defaultSpec = ../default/io.k8s.api.extensions.v1beta1.IngressSpec.dhall
|
|
||||||
in let IntOrString = ../default/io.k8s.apimachinery.pkg.util.intstr.IntOrString.dhall
|
|
||||||
|
|
||||||
-- Our Service type
|
|
||||||
in let Service = ./Service.dhall
|
|
||||||
|
|
||||||
-- Given a service, make a TLS definition with their host and certificate
|
|
||||||
in let makeTLS = \(service : Service) ->
|
|
||||||
{ hosts = Some (List Text) [ service.host ]
|
|
||||||
, secretName = Some Text "${service.name}-certificate"
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Given a service, make an Ingress Rule
|
|
||||||
in let makeRule = \(service : Service) ->
|
|
||||||
{ host = Some Text service.host
|
|
||||||
, http = Some RuleVal
|
|
||||||
{ paths = [ { backend = { serviceName = service.name
|
|
||||||
, servicePort = IntOrString.Int 80
|
|
||||||
}
|
|
||||||
, path = None Text
|
|
||||||
}]}}
|
|
||||||
|
|
||||||
-- Nginx ingress requires a default service as a catchall
|
|
||||||
in let defaultService =
|
|
||||||
{ name = "default"
|
|
||||||
, host = "default.example.com"
|
|
||||||
, version = " 1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
-- List of services
|
|
||||||
in let fooService = ./service-foo.dhall
|
|
||||||
in let services =
|
|
||||||
[ fooService
|
|
||||||
, defaultService
|
|
||||||
]
|
|
||||||
|
|
||||||
-- Some metadata annotations
|
|
||||||
-- NOTE: `dhall-to-yaml` will generate a record with arbitrary keys from a list
|
|
||||||
-- of records where mapKey is the key and mapValue is the value of that key
|
|
||||||
in let genericRecord = List { mapKey : Text, mapValue : Text }
|
|
||||||
in let kv = \(k : Text) -> \(v : Text) -> { mapKey = k, mapValue = v }
|
|
||||||
|
|
||||||
in let annotations = Some genericRecord
|
|
||||||
[ kv "kubernetes.io/ingress.class" "nginx"
|
|
||||||
, kv "kubernetes.io/ingress.allow-http" "false"
|
|
||||||
]
|
|
||||||
|
|
||||||
-- Generate spec from services
|
|
||||||
in let spec = defaultSpec //
|
|
||||||
{ tls = Some (List TLS) (map Service TLS makeTLS services)
|
|
||||||
, rules = Some (List Rule) (map Service Rule makeRule services)
|
|
||||||
}
|
|
||||||
|
|
||||||
in defaultIngress
|
|
||||||
{ metadata = defaultMeta
|
|
||||||
{ name = "nginx" } //
|
|
||||||
{ annotations = annotations }
|
|
||||||
} //
|
|
||||||
{ spec = Some Spec spec } : Ingress
|
|
81
examples/ingressRaw.dhall
Normal file
81
examples/ingressRaw.dhall
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
-- Prelude imports
|
||||||
|
let map = https://raw.githubusercontent.com/dhall-lang/Prelude/v2.0.0/List/map
|
||||||
|
in let Some = https://raw.githubusercontent.com/dhall-lang/Prelude/v2.0.0/Optional/Some
|
||||||
|
in let None = https://raw.githubusercontent.com/dhall-lang/Prelude/v2.0.0/Optional/None
|
||||||
|
|
||||||
|
-- dhall-kubernetes types and defaults
|
||||||
|
in let TLS = ../types/io.k8s.api.extensions.v1beta1.IngressTLS.dhall
|
||||||
|
in let Rule = ../types/io.k8s.api.extensions.v1beta1.IngressRule.dhall
|
||||||
|
in let RuleVal = ../types/io.k8s.api.extensions.v1beta1.HTTPIngressRuleValue.dhall
|
||||||
|
in let Spec = ../types/io.k8s.api.extensions.v1beta1.IngressSpec.dhall
|
||||||
|
in let Ingress = ../types/io.k8s.api.extensions.v1beta1.Ingress.dhall
|
||||||
|
in let defaultIngress = ../default/io.k8s.api.extensions.v1beta1.Ingress.dhall
|
||||||
|
in let defaultMeta = ../default/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta.dhall
|
||||||
|
in let defaultSpec = ../default/io.k8s.api.extensions.v1beta1.IngressSpec.dhall
|
||||||
|
in let IntOrString = ../default/io.k8s.apimachinery.pkg.util.intstr.IntOrString.dhall
|
||||||
|
|
||||||
|
-- Our Service type
|
||||||
|
in let Service = ./Config.dhall
|
||||||
|
in let Config = { services : List Service }
|
||||||
|
|
||||||
|
-- A function to generate an ingress given a configuration
|
||||||
|
in let mkIngress : Config -> Ingress =
|
||||||
|
|
||||||
|
\(config : Config) ->
|
||||||
|
|
||||||
|
-- Given a service, make a TLS definition with their host and certificate
|
||||||
|
let makeTLS = \(service : Service) ->
|
||||||
|
{ hosts = Some (List Text) [ service.host ]
|
||||||
|
, secretName = Some Text "${service.name}-certificate"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Given a service, make an Ingress Rule
|
||||||
|
in let makeRule = \(service : Service) ->
|
||||||
|
{ host = Some Text service.host
|
||||||
|
, http = Some RuleVal
|
||||||
|
{ paths = [ { backend = { serviceName = service.name
|
||||||
|
, servicePort = IntOrString.Int 80
|
||||||
|
}
|
||||||
|
, path = None Text
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Nginx ingress requires a default service as a catchall
|
||||||
|
in let defaultService =
|
||||||
|
{ name = "default"
|
||||||
|
, host = "default.example.com"
|
||||||
|
, version = " 1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- List of services
|
||||||
|
in let services = config.services # [ defaultService ]
|
||||||
|
|
||||||
|
-- Some metadata annotations
|
||||||
|
-- NOTE: `dhall-to-yaml` will generate a record with arbitrary keys from a list
|
||||||
|
-- of records where mapKey is the key and mapValue is the value of that key
|
||||||
|
in let genericRecord = List { mapKey : Text, mapValue : Text }
|
||||||
|
in let kv = \(k : Text) -> \(v : Text) -> { mapKey = k, mapValue = v }
|
||||||
|
|
||||||
|
in let annotations = Some genericRecord
|
||||||
|
[ kv "kubernetes.io/ingress.class" "nginx"
|
||||||
|
, kv "kubernetes.io/ingress.allow-http" "false"
|
||||||
|
]
|
||||||
|
|
||||||
|
-- Generate spec from services
|
||||||
|
in let spec = defaultSpec //
|
||||||
|
{ tls = Some (List TLS) (map Service TLS makeTLS services)
|
||||||
|
, rules = Some (List Rule) (map Service Rule makeRule services)
|
||||||
|
}
|
||||||
|
|
||||||
|
in defaultIngress
|
||||||
|
{ metadata = defaultMeta
|
||||||
|
{ name = "nginx" } //
|
||||||
|
{ annotations = annotations }
|
||||||
|
} //
|
||||||
|
{ spec = Some Spec spec }
|
||||||
|
|
||||||
|
|
||||||
|
-- Here we import our example service, and generate the ingress with it
|
||||||
|
in mkIngress { services = [ ./myConfig.dhall ] }
|
@ -1,22 +1,35 @@
|
|||||||
apiVersion: apps/v1beta2
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
spec:
|
spec:
|
||||||
revisionHistoryLimit: 10
|
revisionHistoryLimit: 20
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: foo
|
app: nginx
|
||||||
|
strategy:
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 5
|
||||||
|
maxUnavailable: 0
|
||||||
|
type: RollingUpdate
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: your-container-service.io/foo:1.0.1
|
- image: nginx:1.15.3
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: foo
|
env: []
|
||||||
|
volumeMounts: []
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
name: nginx
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 80
|
||||||
|
volumes: []
|
||||||
metadata:
|
metadata:
|
||||||
name: foo
|
name: nginx
|
||||||
labels:
|
labels:
|
||||||
app: foo
|
app: nginx
|
||||||
replicas: 2
|
replicas: 2
|
||||||
metadata:
|
metadata:
|
||||||
name: foo
|
name: nginx
|
||||||
|
22
examples/out/deploymentRaw.yaml
Normal file
22
examples/out/deploymentRaw.yaml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: apps/v1beta2
|
||||||
|
kind: Deployment
|
||||||
|
spec:
|
||||||
|
revisionHistoryLimit: 10
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: foo
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: your-container-service.io/foo:1.0.1
|
||||||
|
imagePullPolicy: Always
|
||||||
|
name: foo
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
labels:
|
||||||
|
app: foo
|
||||||
|
replicas: 2
|
||||||
|
metadata:
|
||||||
|
name: foo
|
14
examples/out/service.yaml
Normal file
14
examples/out/service.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: nginx
|
||||||
|
ports:
|
||||||
|
- targetPort: 80
|
||||||
|
port: 80
|
||||||
|
type: NodePort
|
||||||
|
metadata:
|
||||||
|
annotations: {}
|
||||||
|
name: nginx
|
||||||
|
labels:
|
||||||
|
app: nginx
|
8
examples/service.dhall
Normal file
8
examples/service.dhall
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
let config =
|
||||||
|
../api/Service/default
|
||||||
|
//
|
||||||
|
{ name = "nginx"
|
||||||
|
, containerPort = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
in ../api/Service/mkService config
|
@ -15,8 +15,10 @@ import sys
|
|||||||
|
|
||||||
examples_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'examples'))
|
examples_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'examples'))
|
||||||
examples = [
|
examples = [
|
||||||
|
'deploymentRaw',
|
||||||
|
'ingressRaw',
|
||||||
'deployment',
|
'deployment',
|
||||||
'ingress'
|
'service'
|
||||||
]
|
]
|
||||||
|
|
||||||
TERM_FAIL = '\033[91m'
|
TERM_FAIL = '\033[91m'
|
||||||
|
Loading…
Reference in New Issue
Block a user