forked from cadey/xesite
Add blogpost on how I use Dhall for Kubernetes (#110)
* blog: add post on Dhall for Kubernetes * go.sum: tidy * Update dhall-kubernetes-2020-01-25.markdown
This commit is contained in:
parent
2df28c075e
commit
3d20846ab8
|
@ -0,0 +1,435 @@
|
||||||
|
---
|
||||||
|
title: Dhall for Kubernetes
|
||||||
|
date: 2020-01-25
|
||||||
|
tags:
|
||||||
|
- dhall
|
||||||
|
- kubernetes
|
||||||
|
- witchcraft
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dhall for Kubernetes
|
||||||
|
|
||||||
|
Kubernetes is a surprisingly complicated software package. Arguably, it has to
|
||||||
|
be that complicated as a result of the problems it solves being complicated; but
|
||||||
|
managing yaml configuration files for Kubernetes is a complicated task. [YAML][yaml]
|
||||||
|
doesn't have support for variables or type metadata. This means that the
|
||||||
|
validity (or sensibility) of a given Kubernetes configuration file (or files)
|
||||||
|
isn't easy to figure out without using a Kubernetes server.
|
||||||
|
|
||||||
|
[yaml]: https://yaml.org
|
||||||
|
|
||||||
|
In my [last post][cultk8s] about Kubernetes, I mentioned I had developed a tool
|
||||||
|
named [dyson][dyson] in order to help me manage Terraform as well as create
|
||||||
|
Kubernetes manifests from [a template][template]. This works for the majority of
|
||||||
|
my apps, but it is difficult to extend at this point for a few reasons:
|
||||||
|
|
||||||
|
[cultk8s]: https://christine.website/blog/the-cult-of-kubernetes-2019-09-07
|
||||||
|
[dyson]: https://github.com/Xe/within-terraform/tree/master/dyson
|
||||||
|
[template]: https://github.com/Xe/within-terraform/blob/master/dyson/src/dysonPkg/deployment_with_ingress.yaml
|
||||||
|
|
||||||
|
- It assumes that everything passed to it are already valid yaml terms
|
||||||
|
- It doesn't assert the type of any values passed to it
|
||||||
|
- It is difficult to add another container to a given deployment
|
||||||
|
- Environment variables implicitly depend on the presence of a private git repo
|
||||||
|
- It depends on the template being correct more than the output being correct
|
||||||
|
|
||||||
|
So, this won't scale. People in the community have created other solutions for
|
||||||
|
this like [Helm][helm], but a lot of them have some of the same basic problems.
|
||||||
|
Helm also assumes that your template is correct. [Kustomize][kustomize] does
|
||||||
|
help with a lot of the type-safe variable replacements, but it doesn't have the
|
||||||
|
ability to ensure your manifest is valid.
|
||||||
|
|
||||||
|
[helm]: https://helm.sh
|
||||||
|
[kustomize]: https://kustomize.io
|
||||||
|
|
||||||
|
I looked around for alternate solutions for a while and eventually found
|
||||||
|
[Dhall][dhall] thanks to a friend. Dhall is a _statically typed_ configuration
|
||||||
|
language. This means that you can ensure that inputs are _always_ the correct
|
||||||
|
type or the configuration file won't load. There's also a built-in
|
||||||
|
[dhall-to-yaml][dhallyaml] tool that can be used with the [Kubernetes
|
||||||
|
package][dhallk8s] in order to declare Kubernetes manifests in a type-safe way.
|
||||||
|
|
||||||
|
[dhall]: https://dhall-lang.org
|
||||||
|
[dhallyaml]: https://github.com/dhall-lang/dhall-haskell/tree/master/dhall-yaml#dhall-yaml
|
||||||
|
[dhallk8s]: https://github.com/dhall-lang/dhall-kubernetes
|
||||||
|
|
||||||
|
Here's a small example of Dhall and the yaml it generates:
|
||||||
|
|
||||||
|
```dhall
|
||||||
|
-- Mastodon usernames
|
||||||
|
[ { name = "Cadey", mastodon = "@cadey@mst3k.interlinked.me" }
|
||||||
|
, { name = "Nicole", mastodon = "@sharkgirl@mst3k.interlinked.me" }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Which produces:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- mastodon: "@cadey@mst3k.interlinked.me"
|
||||||
|
name: Cadey
|
||||||
|
- mastodon: "@sharkgirl@mst3k.interlinked.me"
|
||||||
|
name: Nicole
|
||||||
|
```
|
||||||
|
|
||||||
|
Which is fine, but we still have the type-safety problem that you would have in
|
||||||
|
normal yaml. Dhall lets us define [record types][dhallrecord] for this data like
|
||||||
|
this:
|
||||||
|
|
||||||
|
[dhallrecord]: http://www.haskellforall.com/2020/01/dhall-year-in-review-2019-2020.html
|
||||||
|
|
||||||
|
```dhall
|
||||||
|
let User =
|
||||||
|
{ Type = { name : Text, mastodon : Optional Text }
|
||||||
|
, default = { name = "", mastodon = None }
|
||||||
|
}
|
||||||
|
|
||||||
|
let users =
|
||||||
|
[ User::{ name = "Cadey", mastodon = Some "@cadey@mst3k.interlinked.me" }
|
||||||
|
, User::{
|
||||||
|
, name = "Nicole"
|
||||||
|
, mastodon = Some "@sharkgirl@mst3k.interlinked.me"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
in users
|
||||||
|
```
|
||||||
|
|
||||||
|
Which produces:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- mastodon: "@cadey@mst3k.interlinked.me"
|
||||||
|
name: Cadey
|
||||||
|
- mastodon: "@sharkgirl@mst3k.interlinked.me"
|
||||||
|
name: Nicole
|
||||||
|
```
|
||||||
|
|
||||||
|
This is type-safe because you cannot add arbitrary fields to User instances
|
||||||
|
without the compiler rejecting it. Let's add an invalid "preferred_language"
|
||||||
|
field to Cadey's instance:
|
||||||
|
|
||||||
|
```
|
||||||
|
-- ...
|
||||||
|
let users =
|
||||||
|
[ User::{
|
||||||
|
, name = "Cadey"
|
||||||
|
, mastodon = Some "@cadey@mst3k.interlinked.me"
|
||||||
|
, preferred_language = "en-US"
|
||||||
|
}
|
||||||
|
-- ...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Which gives us:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ dhall-to-yaml --file example.dhall
|
||||||
|
Error: Expression doesn't match annotation
|
||||||
|
|
||||||
|
{ + preferred_language : …
|
||||||
|
, …
|
||||||
|
}
|
||||||
|
|
||||||
|
4│ User::{ name = "Cadey", mastodon = Some "@cadey@mst3k.interlinked.me",
|
||||||
|
5│ preferred_language = "en-US" }
|
||||||
|
|
||||||
|
example.dhall:4:9
|
||||||
|
```
|
||||||
|
|
||||||
|
Or [this more detailed explanation][explanation] if you add the `--explain` flag
|
||||||
|
to the `dhall-to-yaml` call.
|
||||||
|
|
||||||
|
[explanation]: https://clbin.com/JtVWT
|
||||||
|
|
||||||
|
We tried to do something that violated the contract that the type specified.
|
||||||
|
This means that it's an invalid configuration and is therefore rejected and no
|
||||||
|
yaml file is created.
|
||||||
|
|
||||||
|
The Dhall Kubernetes package specifies record types for _every_ object available
|
||||||
|
by default in Kubernetes. This does mean that the package is incredibly large,
|
||||||
|
but it also makes sure that _everything_ you could possibly want to do in
|
||||||
|
Kubernetes matches what it expects. In the [package
|
||||||
|
documentation][k8sdhalldocs], they give an example where a
|
||||||
|
[Deployment][k8sdeployment] is created.
|
||||||
|
|
||||||
|
[k8sdhalldocs]: https://github.com/dhall-lang/dhall-kubernetes/tree/master/1.15#quickstart---a-simple-deployment
|
||||||
|
[k8sdeployment]: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
|
||||||
|
|
||||||
|
``` dhall
|
||||||
|
-- examples/deploymentSimple.dhall
|
||||||
|
|
||||||
|
-- Importing other files is done by specifying the HTTPS URL/disk location of
|
||||||
|
-- the file. Attaching a sha256 hash (obtained with `dhall freeze`) allows
|
||||||
|
-- the Dhall compiler to cache these files and speed up configuration loads
|
||||||
|
-- drastically.
|
||||||
|
let kubernetes =
|
||||||
|
https://raw.githubusercontent.com/dhall-lang/dhall-kubernetes/1.15/master/package.dhall
|
||||||
|
sha256:4bd5939adb0a5fc83d76e0d69aa3c5a30bc1a5af8f9df515f44b6fc59a0a4815
|
||||||
|
|
||||||
|
let deployment =
|
||||||
|
kubernetes.Deployment::{
|
||||||
|
, metadata = kubernetes.ObjectMeta::{ name = "nginx" }
|
||||||
|
, spec =
|
||||||
|
Some
|
||||||
|
kubernetes.DeploymentSpec::{
|
||||||
|
, replicas = Some 2
|
||||||
|
, template =
|
||||||
|
kubernetes.PodTemplateSpec::{
|
||||||
|
, metadata = kubernetes.ObjectMeta::{ name = "nginx" }
|
||||||
|
, spec =
|
||||||
|
Some
|
||||||
|
kubernetes.PodSpec::{
|
||||||
|
, containers =
|
||||||
|
[ kubernetes.Container::{
|
||||||
|
, name = "nginx"
|
||||||
|
, image = Some "nginx:1.15.3"
|
||||||
|
, ports =
|
||||||
|
[ kubernetes.ContainerPort::{
|
||||||
|
, containerPort = 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
in deployment
|
||||||
|
```
|
||||||
|
|
||||||
|
Which creates the following yaml:
|
||||||
|
|
||||||
|
```
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.15.3
|
||||||
|
name: nginx
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
Dhall's lambda functions can help you break this into manageable chunks. For
|
||||||
|
example, here's a Dhall function that helps create a docker image reference:
|
||||||
|
|
||||||
|
```
|
||||||
|
let formatImage
|
||||||
|
: Text -> Text -> Text
|
||||||
|
= \(repository : Text) -> \(tag : Text) ->
|
||||||
|
"${repository}:${tag}"
|
||||||
|
|
||||||
|
in formatImage "xena/christinewebsite" "latest"
|
||||||
|
```
|
||||||
|
|
||||||
|
Which outputs `xena/christinewebsite:latest` when passed to `dhall text`.
|
||||||
|
|
||||||
|
All of this adds up into a powerful toolset that lets you express Kubernetes
|
||||||
|
configuration in a way that does what you want without as many headaches.
|
||||||
|
|
||||||
|
Most of my apps on Kubernetes need only a few generic bits of configuration:
|
||||||
|
|
||||||
|
- Their name
|
||||||
|
- What port should be exposed
|
||||||
|
- The domain that this service should be exposed on
|
||||||
|
- How many replicas of the service are needed
|
||||||
|
- Which Let's Encrypt Issuer to use (currently only `"prod"` or `"staging"`)
|
||||||
|
- The [configuration variables of the service][12factorconfig]
|
||||||
|
- Any other containers that may be needed for the service
|
||||||
|
|
||||||
|
[12factorconfig]: https://12factor.net/config
|
||||||
|
|
||||||
|
From here, I defined all of the [bits and pieces][kubermemeshttp] for the
|
||||||
|
Kubernetes manifests that Dyson produces and then created a `Config` type that
|
||||||
|
helps to template them out. Here's my [`Config` type
|
||||||
|
definition][configdefinition]:
|
||||||
|
|
||||||
|
[kubermemeshttp]: https://tulpa.dev/cadey/kubermemes/src/branch/master/k8s/http
|
||||||
|
[configdefinition]: https://tulpa.dev/cadey/kubermemes/src/branch/master/k8s/app/config.dhall
|
||||||
|
|
||||||
|
```dhall
|
||||||
|
let kubernetes = ../kubernetes.dhall
|
||||||
|
|
||||||
|
in { Type =
|
||||||
|
{ name : Text
|
||||||
|
, appPort : Natural
|
||||||
|
, image : Text
|
||||||
|
, domain : Text
|
||||||
|
, replicas : Natural
|
||||||
|
, leIssuer : Text
|
||||||
|
, envVars : List kubernetes.EnvVar.Type
|
||||||
|
, otherContainers : List kubernetes.Container.Type
|
||||||
|
}
|
||||||
|
, default =
|
||||||
|
{ name = ""
|
||||||
|
, appPort = 5000
|
||||||
|
, image = ""
|
||||||
|
, domain = ""
|
||||||
|
, replicas = 1
|
||||||
|
, leIssuer = "staging"
|
||||||
|
, envVars = [] : List kubernetes.EnvVar.Type
|
||||||
|
, otherContainers = [] : List kubernetes.Container.Type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then I defined a `makeApp` function that creates everything I need to deploy my
|
||||||
|
stuff on Kubernetes:
|
||||||
|
|
||||||
|
```dhall
|
||||||
|
let Prelude = ../Prelude.dhall
|
||||||
|
|
||||||
|
let kubernetes = ../kubernetes.dhall
|
||||||
|
|
||||||
|
let typesUnion = ../typesUnion.dhall
|
||||||
|
|
||||||
|
let deployment = ../http/deployment.dhall
|
||||||
|
|
||||||
|
let ingress = ../http/ingress.dhall
|
||||||
|
|
||||||
|
let service = ../http/service.dhall
|
||||||
|
|
||||||
|
let Config = ../app/config.dhall
|
||||||
|
|
||||||
|
let K8sList = ../app/list.dhall
|
||||||
|
|
||||||
|
let buildService =
|
||||||
|
\(config : Config.Type)
|
||||||
|
-> let myService = service config
|
||||||
|
|
||||||
|
let myDeployment = deployment config
|
||||||
|
|
||||||
|
let myIngress = ingress config
|
||||||
|
|
||||||
|
in K8sList::{
|
||||||
|
, items =
|
||||||
|
[ typesUnion.Service myService
|
||||||
|
, typesUnion.Deployment myDeployment
|
||||||
|
, typesUnion.Ingress myIngress
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
in buildService
|
||||||
|
```
|
||||||
|
|
||||||
|
And used it to deploy the [h language website][hlang]:
|
||||||
|
|
||||||
|
[hlang]: https://h.christine.website
|
||||||
|
|
||||||
|
```dhall
|
||||||
|
let makeApp = ../app/make.dhall
|
||||||
|
|
||||||
|
let Config = ../app/config.dhall
|
||||||
|
|
||||||
|
let cfg =
|
||||||
|
Config::{
|
||||||
|
, name = "hlang"
|
||||||
|
, appPort = 5000
|
||||||
|
, image = "xena/hlang:latest"
|
||||||
|
, domain = "h.christine.website"
|
||||||
|
, leIssuer = "prod"
|
||||||
|
}
|
||||||
|
|
||||||
|
in makeApp cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
Which produces the following Kubernetes config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
items:
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
|
external-dns.alpha.kubernetes.io/hostname: h.christine.website
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "120"
|
||||||
|
labels:
|
||||||
|
app: hlang
|
||||||
|
name: hlang
|
||||||
|
namespace: apps
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 5000
|
||||||
|
targetPort: 5000
|
||||||
|
selector:
|
||||||
|
app: hlang
|
||||||
|
type: ClusterIP
|
||||||
|
- apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: hlang
|
||||||
|
namespace: apps
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: hlang
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: hlang
|
||||||
|
name: hlang
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: xena/hlang:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
name: web
|
||||||
|
ports:
|
||||||
|
- containerPort: 5000
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
- apiVersion: networking.k8s.io/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
certmanager.k8s.io/cluster-issuer: letsencrypt-prod
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
labels:
|
||||||
|
app: hlang
|
||||||
|
name: hlang
|
||||||
|
namespace: apps
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: h.christine.website
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
serviceName: hlang
|
||||||
|
servicePort: 5000
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- h.christine.website
|
||||||
|
secretName: prod-certs-hlang
|
||||||
|
kind: List
|
||||||
|
```
|
||||||
|
|
||||||
|
And when I applied it on my Kubernetes cluster, it worked the first time and had
|
||||||
|
absolutely no effect on the existing configuration.
|
||||||
|
|
||||||
|
In the future, I hope to expand this to allow for multiple deployments (IE: a
|
||||||
|
chatbot running in a separate deployment than a web API the chatbot depends on
|
||||||
|
or non-web projects in general) as well as supporting multiple Kubernetes
|
||||||
|
namespaces.
|
||||||
|
|
||||||
|
Dhall is probably the most viable replacement to Helm or other Kubernetes
|
||||||
|
templating tools I have found in recent memory. I hope that it will be used by
|
||||||
|
more people to help with configuration management, but I can understand that
|
||||||
|
that may not happen. At least it works for me.
|
||||||
|
|
||||||
|
If you want to learn more about Dhall, I suggest checking out the following
|
||||||
|
links:
|
||||||
|
|
||||||
|
- [The Dhall Language homepage](https://dhall-lang.org)
|
||||||
|
- [Learn Dhall in Y Minutes](https://learnxinyminutes.com/docs/dhall/)
|
||||||
|
- [The Dhall Language GitHub Organization](https://github.com/dhall-lang)
|
||||||
|
|
||||||
|
I hope this was helpful and interesting. Be well.
|
22
go.sum
22
go.sum
|
@ -9,8 +9,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/celrenheit/sandflake v0.0.0-20190410195419-50a943690bc2 h1:/BpnZPo/sk1vPlt62dLya5KCn7PN9ZBDrpTGlQzgUZI=
|
github.com/celrenheit/sandflake v0.0.0-20190410195419-50a943690bc2 h1:/BpnZPo/sk1vPlt62dLya5KCn7PN9ZBDrpTGlQzgUZI=
|
||||||
github.com/celrenheit/sandflake v0.0.0-20190410195419-50a943690bc2/go.mod h1:7L8gY0+4GYeBc9TvqVuDUq7tXuM6Sj7llnt7HkVwWlQ=
|
github.com/celrenheit/sandflake v0.0.0-20190410195419-50a943690bc2/go.mod h1:7L8gY0+4GYeBc9TvqVuDUq7tXuM6Sj7llnt7HkVwWlQ=
|
||||||
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
@ -26,14 +25,12 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||||
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
@ -60,15 +57,12 @@ github.com/povilasv/prommod v0.0.12/go.mod h1:GnuK7wLoVBwZXj8bhbJNx/xFSldy7Q49A4
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
|
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
|
|
||||||
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
|
|
||||||
github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc=
|
github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc=
|
||||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
@ -77,8 +71,7 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
|
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
|
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
|
||||||
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
@ -114,8 +107,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o=
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
@ -125,10 +117,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
|
|
Loading…
Reference in New Issue