From afa1b88e4987fe8a52e125a294f0e66b83e2837c Mon Sep 17 00:00:00 2001 From: Fabrizio Ferrai Date: Sat, 2 Jun 2018 23:43:49 +0300 Subject: [PATCH 1/2] Rewrite readme adding some examples --- README.md | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 246 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a4cbd25f..b42ac0e9 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,254 @@ -# Usage +# `dhall-kubernetes` -## Typed +Dhall bindings to Kubernetes. +This will let you typecheck, template and modularize your Kubernetes definitions with [Dhall][dhall-lang]. + +## Quick start + +In the `types` folder you'll find the types for the Kubernetes definitions. E.g. [here's][Deployment] the type for a Deployment. + +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` 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 Deployment is [this function][Deployment-default]. + +Since this might sound a bit abstract, let's go with some examples. + +### Example: Deployment + +Let's say we have several services, whose configuration has this type: +```haskell +-- Service.dhall +{ name : Text +, host : Text +, version : Text +} +``` + +So a configuration for a service might look like this: +```haskell +-- service-foo.dhall +{ name = "foo" +, host = "foo.example.com" +, version = "1.0.1" +} +``` + +We can then make a Deployment object for this service: +```haskell +-- deployment.yaml.dhall + +-- 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 + +-- Our Service type +in let Service = ./Service.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 Integer 2 +, revisionHistoryLimit = Some Integer 10 +} + +-- and here's the Deployment +in defaultDeployment +{ apiVersion = "extensions/v1beta1" +, kind = "Deployment" +, metadata = defaultMeta { name = fooService.name } +} // +{ spec = Some Spec spec } : Deployment + +``` + +We convert it to yaml with: ```bash -cat example-deployment.dhall | dhall-to-yaml | kubectl apply -f - +dhall-to-yaml --omitNull < deployment.yaml.dhall ``` -## Untyped +And we get: +```yaml +apiVersion: extensions/v1beta1 +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 +``` + + +### Example: Ingress + +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 +-- ingress.yaml.dhall + +-- 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 + +-- 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 = "80" + } + , path = None Text + }]}} + +-- Nginx ingress requires a default service as a catchall +in let defaultService = { name = "default", host = "default.example.com" } + +-- 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 +{ apiVersion = "extensions/v1beta1" +, kind = "Ingress" +, metadata = defaultMeta + { name = "nginx" } // + { annotations = annotations } +} // +{ spec = spec } : Ingress ``` -cat example-deployment-no-types.dhall | dhall-to-yaml | kubectl apply -f - + +As usual we get the yaml out by running: + +```bash +dhall-to-yaml --omitNull < ingress.yaml.dhall ``` + +And we get: +```yaml +apiVersion: extensions/v1beta1 +kind: Ingress +spec: + rules: + - http: + paths: + - backend: + servicePort: '80' + serviceName: foo + host: foo.example.com + - http: + paths: + - backend: + servicePort: '80' + serviceName: default + host: default.example.com + tls: + - hosts: + - foo.example.com + secretName: foo-certificate + - hosts: + - default.example.com + secretName: default-certificate +metadata: + annotations: + kubernetes.io/ingress.class: nginx + kubernetes.io/ingress.allow-http: 'false' + name: nginx +``` + + +[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 +[Deployment-default]: https://github.com/dhall-lang/dhall-kubernetes/blob/master/default/io.k8s.api.apps.v1beta2.Deployment.dhall From 89edbdca9dec5c0001f75de5e5866cba4738883a Mon Sep 17 00:00:00 2001 From: Fabrizio Ferrai Date: Sat, 2 Jun 2018 23:44:15 +0300 Subject: [PATCH 2/2] Remove outdated examples --- example-deployment-no-types.dhall | 25 ------------------------- example-deployment.dhall | 7 ------- 2 files changed, 32 deletions(-) delete mode 100644 example-deployment-no-types.dhall delete mode 100644 example-deployment.dhall diff --git a/example-deployment-no-types.dhall b/example-deployment-no-types.dhall deleted file mode 100644 index 0b803129..00000000 --- a/example-deployment-no-types.dhall +++ /dev/null @@ -1,25 +0,0 @@ -let name = "nginx" in -let port = 80 in -{ - apiVersion = "apps/v1beta1", - kind = "Deployment", - metadata = { name = name }, - spec = { - template = { - metadata = { - labels = { - app = name, - }, - }, - spec = { - containers = [ - { - name = name, - image = "${name}:1.7.9", - ports = [ {containerPort = port } ] - } - ] - } - }, - }, -} diff --git a/example-deployment.dhall b/example-deployment.dhall deleted file mode 100644 index 45ed8722..00000000 --- a/example-deployment.dhall +++ /dev/null @@ -1,7 +0,0 @@ -{ - apiVersion = ["v1beta2"] : Optional Text, - kind = ["Deployment"] : Optional Text, - metadata = [] : Optional ./out/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta.dhall , - spec = [] : Optional ./out/io.k8s.api.apps.v1beta2.DeploymentSpec.dhall , - status = [] : Optional ./out/io.k8s.api.apps.v1beta2.DeploymentStatus.dhall , -} : ./out/io.k8s.api.apps.v1beta2.Deployment.dhall