Add high level API (#34)

This adds a high level API (+ tests) for:
- Deployment.v1
- Service.v1
This commit is contained in:
Fabrizio Ferrai 2018-09-13 13:33:48 +03:00 committed by GitHub
parent 11a986ae11
commit f8be1d55bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 867 additions and 339 deletions

349
README.md
View File

@ -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
View 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
View 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
View File

@ -0,0 +1,4 @@
{ mountPath : Text
, name : Text
, readOnly : Optional Bool
}

5
api/Deployment/Probe Normal file
View File

@ -0,0 +1,5 @@
{ initial : Natural
, period : Natural
, port : Natural
, path : Text
}

View 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
View 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
View 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

View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
< ClusterIP : {}
| NodePort : {}
| LoadBalancer : {}
| ExternalName : {}
>

6
api/Service/default Normal file
View 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
View 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

View File

@ -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
'' ''

View File

@ -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

View 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

View File

@ -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
View 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 ] }

View File

@ -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

View 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
View 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
View File

@ -0,0 +1,8 @@
let config =
../api/Service/default
//
{ name = "nginx"
, containerPort = 80
}
in ../api/Service/mkService config

View File

@ -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'