diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..260dc281 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,41 @@ +name: CI + +on: [push, pull_request] + +jobs: + check: + name: Check + runs-on: ubuntu-latest + # Execute the checks inside the container instead the VM. + container: golangci/golangci-lint:v1.24.0-alpine + steps: + - uses: actions/checkout@v1 + - run: ./hack/scripts/check.sh + + unit-test: + name: Unit test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-go@v2-beta + with: + go-version: 1.14 + - run: make ci-unit-test + + integration-test: + name: Integration test + runs-on: ubuntu-latest + needs: [check, unit-test] + strategy: + matrix: + kubernetes: [1.15.7, 1.16.4, 1.17.0, 1.18.0] + env: + KIND_VERSION: v0.7.0 + steps: + - uses: actions/checkout@v1 + - run: curl -Lo kind https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64 && chmod +x kind && sudo mv kind /usr/local/bin/ + - run: curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v${{ matrix.kubernetes }}/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ + - run: KUBERNETES_VERSION=${{ matrix.kubernetes }} make ci-integration-test + + + diff --git a/.gitignore b/.gitignore index 0d16cc57..d5ae9b87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,23 @@ -# Binaries for programs and plugins +# Binaries for programs and plugins. *.exe *.dll *.so *.dylib -# Test binary, build with `go test -c` +# Test binary, build with `go test -c`. *.test -# Output of the go coverage tool, specifically when used with LiteIDE +# Output of the go coverage tool, specifically when used with LiteIDE. *.out -# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736. .glide/ -# binary +# Binary. bin/ -# vendor -vendor/ \ No newline at end of file +# Vendor. +vendor/ + +# Test coverage. +.test_coverage.txt \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7ff623bd..00000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -sudo: required -dist: trusty -services: - - docker - -language: go -go: - - "1.13" - -before_install: - - curl -Lo kind https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64 && chmod +x kind && sudo mv kind /usr/local/bin/ - - curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ - -script: - - make ci - -env: - global: - - GO111MODULE=on - - KIND_VERSION=v0.6.1 - - KUBERNETES_VERSION=1.15.6 diff --git a/CHANGELOG.md b/CHANGELOG.md index da6ad1d1..a9a77ca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,32 @@ ## [unreleased] -- Support for Kubernetes 1.13. +NOTE: Breaking release in controllers. + +- Refactor controller package. +- Refactor metrics package. +- Refactor log package. +- Remove operator concept and remove CRD initialization in favor of using only + controllers and let the CRD initialization outside Kooper (e.g CRD yaml). +- Default resync time to 3 minutes. +- Default workers to 3. +- Disable retry handling on controllers in case of error by default. +- Remove tracing. +- Minimum Go version v1.13 (error wrapping required). +- Refactor Logger with structured logging. +- Add Logrus helper wrapper. +- Refactor to simplify the retrievers. +- Refactor metrics recorder implementation including the prometheus backend. +- Refactor internal controller queue into a decorator implementation approach. +- Remove `Delete` method from `controller.Handler` and simplify to only `Handle` method +- Add `DisableResync` flag on controller configuration to disable the resync of all resources. + +## [0.8.0] - 2019-12-11 + +- Support for Kubernetes 1.15. + +## [0.7.0] - 2019-11-26 + +- Support for Kubernetes 1.14. ## [0.6.0] - 2019-06-01 @@ -83,7 +109,9 @@ This release breaks controllers constructors to allow passing a metrics recorder - sequential controller implementation. - Dependencies managed by dep and vendored. -[unreleased]: https://github.com/spotahome/kooper/compare/v0.6.0...HEAD +[unreleased]: https://github.com/spotahome/kooper/compare/v0.8.0...HEAD +[0.8.0]: https://github.com/spotahome/kooper/compare/v0.7.0...v0.8.0 +[0.7.0]: https://github.com/spotahome/kooper/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/spotahome/kooper/compare/v0.5.1...v0.6.0 [0.5.1]: https://github.com/spotahome/kooper/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/spotahome/kooper/compare/v0.4.1...v0.5.0 diff --git a/Makefile b/Makefile index 2bc67b77..007ee8a3 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,8 @@ - -# Name of this service/application SERVICE_NAME := kooper - -# Path of the go service inside docker -DOCKER_GO_SERVICE_PATH := /src - -# Shell to use for running scripts SHELL := $(shell which bash) - -# Get docker path or an empty string DOCKER := $(shell command -v docker) - -# Get the main unix group for the user running make (to be used by docker-compose later) +OSTYPE := $(shell uname) GID := $(shell id -g) - -# Get the unix user id for the user running make (to be used by docker-compose later) UID := $(shell id -u) # cmds @@ -22,82 +10,51 @@ UNIT_TEST_CMD := ./hack/scripts/unit-test.sh INTEGRATION_TEST_CMD := ./hack/scripts/integration-test.sh CI_INTEGRATION_TEST_CMD := ./hack/scripts/integration-test-kind.sh MOCKS_CMD := ./hack/scripts/mockgen.sh -DOCKER_RUN_CMD := docker run -v ${PWD}:$(DOCKER_GO_SERVICE_PATH) --rm -it $(SERVICE_NAME) -RUN_EXAMPLE_POD_ECHO := go run ./examples/echo-pod-controller/cmd/* --development -RUN_EXAMPLE_POD_ECHO_ONEFILE := go run ./examples/onefile-echo-pod-controller/main.go --development -RUN_EXAMPLE_POD_TERM := go run ./examples/pod-terminator-operator/cmd/* --development -DEPS_CMD := GO111MODULE=on go mod tidy && GO111MODULE=on go mod vendor -K8S_VERSION := "1.15.6" -SET_K8S_DEPS_CMD := GO111MODULE=on go mod edit \ - -require=k8s.io/apiextensions-apiserver@kubernetes-${K8S_VERSION} \ - -require=k8s.io/client-go@kubernetes-${K8S_VERSION} \ - -require=k8s.io/apimachinery@kubernetes-${K8S_VERSION} \ - -require=k8s.io/api@kubernetes-${K8S_VERSION} \ - -require=k8s.io/kubernetes@v${K8S_VERSION} && \ - $(DEPS_CMD) +DOCKER_RUN_CMD := docker run --env ostype=$(OSTYPE) -v ${PWD}:/src --rm -it ${SERVICE_NAME} +DEPS_CMD := go mod tidy +CHECK_CMD := ./hack/scripts/check.sh + -# environment dirs -DEV_DIR := docker/dev +help: ## Show this help + @echo "Help" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[93m %s\n", $$1, $$2}' -# The default action of this Makefile is to build the development docker image .PHONY: default -default: build +default: help -# Test if the dependencies we need to run this Makefile are installed -.PHONY: deps-development -deps-development: -ifndef DOCKER - @echo "Docker is not available. Please install docker" - @exit 1 -endif -# Build the development docker image .PHONY: build -build: +build: ## Build the development docker image. docker build -t $(SERVICE_NAME) --build-arg uid=$(UID) --build-arg gid=$(GID) -f ./docker/dev/Dockerfile . -# Dependency stuff. -.PHONY: set-k8s-deps -set-k8s-deps: - $(SET_K8S_DEPS_CMD) - .PHONY: deps -deps: +deps: ## Updates the required dependencies. $(DEPS_CMD) -# Test stuff in dev -.PHONY: unit-test -unit-test: build - $(DOCKER_RUN_CMD) /bin/sh -c '$(UNIT_TEST_CMD)' .PHONY: integration-test -integration-test: build +integration-test: build ## Runs integration tests out of CI. echo "[WARNING] Requires a kubernetes cluster configured (and running) on your kubeconfig!!" $(INTEGRATION_TEST_CMD) + .PHONY: test -test: unit-test +test: build ## Runs unit tests out of CI. + $(DOCKER_RUN_CMD) /bin/sh -c '$(UNIT_TEST_CMD)' + +.PHONY: check +check: build ## Runs checks. + @$(DOCKER_RUN_CMD) /bin/sh -c '$(CHECK_CMD)' -# Test stuff in ci .PHONY: ci-unit-test -ci-unit-test: +ci-unit-test: ## Runs unit tests in CI. $(UNIT_TEST_CMD) + .PHONY: ci-integration-test -ci-integration-test: +ci-integration-test: ## Runs integration tests in CI. $(CI_INTEGRATION_TEST_CMD) -.PHONY: ci + +.PHONY: ci ## Runs all tests in CI. ci: ci-unit-test ci-integration-test -# Mocks stuff in dev .PHONY: mocks -mocks: build +mocks: build ## Generates mocks. $(DOCKER_RUN_CMD) /bin/sh -c '$(MOCKS_CMD)' - -# Run examples. -.PHONY: controller-example -controller-example: - $(RUN_EXAMPLE_POD_ECHO) -.PHONY: controller-example-onefile -controller-example-onefile: - $(RUN_EXAMPLE_POD_ECHO_ONEFILE) -.PHONY: operator-example -operator-example: - $(RUN_EXAMPLE_POD_TERM) diff --git a/README.md b/README.md index 8bcb883e..391619f0 100644 --- a/README.md +++ b/README.md @@ -1,180 +1,192 @@ # Kooper [![Build Status][travis-image]][travis-url] [![Go Report Card][goreport-image]][goreport-url] [![GoDoc][godoc-image]][godoc-url] -Kooper is a simple Go library to create Kubernetes [operators](https://coreos.com/operators/) and [controllers](https://github.com/kubernetes/community/blob/master/contributors/devel/controllers.md). +Kooper is a Go library to create simple and flexible [controllers]/operators, in a fast, decoupled and easy way. -## What is Kooper? +In other words, is a small alternative to big frameworks like [Kubebuilder] or [operator-framework]. -Kooper is a set of utilities packed as a library or framework to easily create Kubernetes controllers and operators. +**Library refactored (`v2`), for `v2` use `import "github.com/spotahome/kooper/v2"`** -There is a little of discussion of what a controller and what an operator is, see [here](https://stackoverflow.com/questions/47848258/kubernetes-controller-vs-kubernetes-operator) for more information. +## Features -In Kooper the concepts of controller an operator are very simple, a controller controls the state of a resource in Kubernetes, and an operator is a controller that initializes custom resources (CRD) and controls the state of this custom resource. +- Easy usage and fast to get it working. +- Extensible (Kooper doesn't get in your way). +- Simple core concepts (`Retriever` + `Handler` == controller && controller == operator). +- Metrics (extensible with Prometheus already implementated). +- Ready for core Kubernetes resources (pods, ingress, deployments...) and CRDs. +- Optional leader election system for controllers. -## Features +## V0 vs V2 + +First of all, we used `v2` instead of `v[01]`, because it changes the library as a whole, theres no backwards compatibility, +`v0` is stable and used in production, although you eventually will want to update to `v2` becasuse `v0` will not be updated. -- Easy and decoupled library. -- Well structured and a clear API. -- Remove all duplicated code from every controller and operator. -- Uses the tooling already created by Kubernetes. -- Remove complexity from operators and controllers so the focus is on domain logic. -- Easy to mock and extend functionality (Go interfaces!). -- Only support CRD, no TPR support (Kubernetes >=1.7). -- Controller metrics (with a [Grafana dashboard][grafana-dashboard] based on Prometheus metrics backend). -- Leader election. -- Tracing with [Opentracing][opentracing-url]. +Import with: -## Example +```golang +import "github.com/spotahome/kooper/v2" +``` -It can be seen how easy is to develop a controller or an operator in kooper looking at the [documentation](docs). +Regarding the changes... To know all of them check the changelog but mainly we simplified everything. The +most relevant changes you will need to be aware and could impact are: -This is a simple pod log controller example ([full running example here](https://github.com/spotahome/kooper/blob/master/examples/onefile-echo-pod-controller/main.go)): +- Before there were concepts like `operator` and `controller`, now only `controller` (this is at library level, you can continue creating controllers/operators). +- Before the CRD management was inside the library, now this should be managed outside Kooper. + - You can use [this][kube-code-generator] to generate these manifests to register outside Kooper. + - This is because controllers and CRDs have different lifecycles. +- Refactored Prometheus metrics to be more reliable, so you will need to change dashboards/alerts. +- `Delete` event removed because wasn't reliable (Check `Garbage-collection` section). -```go -// Initialize resources like logger and kubernetes client -//... +## Getting started + +The simplest example that prints pods would be this: -// Create our retriever so the controller knows how to get/listen for pod events. -retr := &retrieve.Resource{ - Object: &corev1.Pod{}, - ListerWatcher: &cache.ListWatch{ +```go + // Create our retriever so the controller knows how to get/listen for pod events. + retr := controller.MustRetrieverFromListerWatcher(&cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { return k8scli.CoreV1().Pods("").List(options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { return k8scli.CoreV1().Pods("").Watch(options) }, - }, -} + }) -// Our domain logic that will print every add/sync/update and delete event. -hand := &handler.HandlerFunc{ - AddFunc: func(_ context.Context, obj runtime.Object) error { + // Our domain logic that will print all pod events. + hand := controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error { pod := obj.(*corev1.Pod) - log.Infof("Pod added: %s/%s", pod.Namespace, pod.Name) - return nil - }, - DeleteFunc: func(_ context.Context, s string) error { - log.Infof("Pod deleted: %s", s) + logger.Infof("Pod event: %s/%s", pod.Namespace, pod.Name) return nil - }, -} - -// Create the controller that will refresh every 30 seconds. -ctrl := controller.NewSequential(30*time.Second, hand, retr, nil, log) -stopC := make(chan struct{}) -if err := ctrl.Run(stopC); err != nil { - log.Errorf("error running controller: %s", err) - os.Exit(1) -} -os.Exit(0) + }) + + // Create the controller with custom configuration. + cfg := &controller.Config{ + Name: "example-controller", + Handler: hand, + Retriever: retr, + Logger: logger, + } + ctrl, err := controller.New(cfg) + if err != nil { + return fmt.Errorf("could not create controller: %w", err) + } + + // Start our controller. + ctrl.Run(make(chan struct{})) ``` -The above shows that it is very easy to get a controller working in less than 100 lines of code. How it works can be demonstrated by running the controller from this repository. +## Kubernetes version compatibility -```bash -go run ./examples/onefile-echo-pod-controller/main.go -``` +Kooper at this moment uses as base `v1.17`. But [check the integration test in CI][ci] to know the supported versions. -or directly using docker (Note you are binding kubernetes configuration on the container so kooper can connect to kubernetes cluster). +## When should I use Kooper? -```bash -docker run \ - --rm -it \ - -v ${HOME}/.kube:/root/.kube:ro \ - golang:1.10 \ - /bin/bash -c "go get github.com/spotahome/kooper/... && cd /go/src/github.com/spotahome/kooper && go run ./examples/onefile-echo-pod-controller/main.go" -``` +### Alternatives -## Motivation +What is the difference between kooper and alternatives like [Kubebuilder] or [operator-framework]? -The state of art in the operators/controllers moves fast, a lot of new operators are being published every day. Most of them have the same "infrastructure" code refering Kubernetes operators/controllers and bootstrapping a new operator can be slow or repetitive. +Kooper embraces the Go philosophy of having small simple components/libs and use them as you wish in combination of others, instead of trying to solve every use case and imposing everything by sacriying flexbility and adding complexity. -At this moment there is no standard, although there are some projects like [rook operator kit](https://github.com/rook/operator-kit) or [Giantswarm operator kit](https://github.com/giantswarm/operatorkit) that are trying to create it. +As an example using the web applications world as reference: We could say that Kooper is more like Go HTTP router/libs, on the other side, Kubebuilder and operator-framework are like Ruby on rails/Django style frameworks. -Spotahome studied these projects before developing Kooper and they didn't fit the requirements: +For example Kubebuilder comes with: -- Clear and maintanable code. -- Easy to test and mock. -- Well tested library. -- Easy and clear programming API. -- Good abstraction and structure to focus on domain logic (the meat of the controller). -- Reduce complexity in all the operators and controllers that use the library. -- Not only operators, controllers as first class citizen also. +- Admission webhooks. +- Folder/package structure conventions. +- CRD clients generation. +- RBAC manifest generation. +- Not pluggable/flexible usage of metrics, logging, HTTP/K8s API clients... +- ... -## Installing +Kooper instead solves most of the core controller/operator problems but as a simple, small and flexible library, and let the other problems (like admission webhooks) be solved by other libraries specialiced on that. e.g -Any dependency manager can get Kooper or directly with go get the latest version: +- Use whatever you want to create your CRD clients, maybe you don't have CRDs at all! (e.g [kube-code-generator]). +- You can setup your admission webhooks outside your controller by using other libraries like (e.g [Kubewebhook]). +- You can create your RBAC manifests as you wish and evolve while you develop your controller. +- Set you prefered logging system/style (comes with logrus implementation). +- Implement your prefered metrics backend (comes with Prometheus implementaion). +- Use your own Kubernetes clients (Kubernetes go library, implemented by your own for a special case...). +- ... -```bash -go get -u github.com/spotahome/kooper -``` +### Simplicty VS optimization -## Using Kooper as a dependency +For example Kubebuilder sacrifies API simplicity/client in favor of aggresive cache usage. Kooper instead embraces simplicity over optimization: -Managing a project that uses different kubernetes libs as dependencies can be tricky at first because of the different versions of all these libraries(apimachinery, client-go, api...). [Here][dependency-example] you have an example of how would you use kooper as a dependency in a project and setting the kubernetes libraries to the version that you want along with kooper (using [dep][dep-project]). +- Most of the controller/operators don't need this kind of optimizations. Adding complexity on all controllers for a small portion of the controllers out there is not the best approach for most of the developers. +- If you need optimization related with Kubernetes resources, you can use Kubebuilder or implement your own solution for that specific use case like a cache based Kubernetes client or service. +- If you need optimization and is not related with Kubernetes API itself, it doesn't matter the controller library optimization. -## Compatibility matrix +## More examples -| | Kubernetes <=1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 | Kubernetes 1.14 | -| ----------- | ---------------- | --------------- | --------------- | --------------- | --------------- | --------------- | -| kooper 0.1 | ✓ | ? | ? | ? | ? | ? | -| kooper 0.2 | ✓ | ? | ? | ? | ? | ? | -| kooper 0.3 | ?+ | ✓ | ? | ? | ? | ? | -| kooper 0.4 | ?+ | ✓ | ? | ? | ? | ? | -| kooper 0.5 | ? | ?+ | ✓ | ? | ? | ? | -| kooper 0.6 | ? | ? | ?+ | ✓ | ? | ? | -| kooper HEAD | ? | ? | ? | ?+ | ✓? | ? | +On the [examples] folder you have different examples, like regular controllers, operators, metrics based, leader election, multiresource type controllers... -Based on this matrix Kooper needs different versions of Kubernetes dependencies. +## Core concepts -An example would be. If the cluster that will use kooper operators/controllers is a 1.9.x Kubernetes cluster, the version of kooper would be `0.2.x` and the kubernetes libraries in `1.9.x` compatibility style. For example the project that uses kooper as a dependency, its dep file would be something like this: +Concept doesn't do a distinction between Operators and controllers, all are controllers, the difference of both is on what resources are retrieved. -```yaml -... +A controller is based on 3 simple concepts: -[[override]] - name = "k8s.io/api" - version = "kubernetes-1.9.6" +### Retriever -[[override]] - name = "k8s.io/apimachinery" - version = "kubernetes-1.9.6" +The component that lists and watch the resources the controller will handle when there is a change. Kooper comes with some helpers to create fast retrievers: -[[override]] - name = "k8s.io/client-go" - version = "kubernetes-1.9.6" +- `Retriever`: The core retriever it needs to implement list (list objects), and watch, subscribe to object changes. +- `RetrieverFromListerWatcher`: Converts a Kubernetes ListerWatcher into a kooper Retriever. -[[constraint]] - name = "github.com/spotahome/kooper" - version = "0.2.0" +The `Retriever` can be based on Kubernetes base resources (Pod, Deployment, Service...) or based on CRDs, theres no distinction. -... -``` +The `Retriever` is an interface so you can use the middleware/wrapper/decorator pattern to extend (e.g add custom metrics). + +### Handler + +Kooper handles all the events on the same handler: + +- `Handler`: The interface that knows how to handle kubernetes objects. +- `HandlerFunc`: A helper that gets a `Handler` from a function so you don't need to create a new type to define your `Handler`. + +The `Handler` is an interface so you can use the middleware/wrapper/decorator pattern to extend (e.g add custom metrics). + +### Controller + +The controller is the component that uses the `Handler` and `Retriever` to start a feedback loop controller process: + +- On the first start it will use `controller.Retriever.List` to get all the resources and pass them to the `controller.Handler`. +- Then it will call `controller.Handler` for every change done in the resources using the `controller.Retriever.Watcher`. +- At regular intervals (3 minute by default) it will call `controller.Handler` with all resources in case we have missed a `Watch` event. + +## Other concepts + +### Leader election + +Check [Leader election](docs/leader-election.md). -## Documentation +### Garbage collection -Kooper comes with different topics as documentation. +Kooper only handles the events of resources that exist, these are triggered when the resources being watched are updated or created. There is no delete event, so in order to clean the resources you have 2 ways of doing these: -### Core +- If your controller creates as a side effect new Kubernetes resources you can use [owner references][owner-ref] on the created objects. +- If you want a more flexible clean up process (e.g clean from a database or a 3rd party service) you can use [finalizers], check the [pod-terminator-operator][finalizer-example] example. -- [Basic concepts](docs/concepts.md) -- [Controller tutorial](docs/controller-tutorial.md) -- [Operator tutorial](docs/operator-tutorial.md) +### Multiresource or secondary resources -### Other +Sometimes we have controllers that work on a main or primary resource and we also want to handle the events of a secondary resource that is based on the first one. For example, a deployment controller that watches the pods (secondary) that belong to the deployment (primary) handled. -- [Metrics](docs/metrics.md) -- [Logger](docs/logger.md) -- [Leader election](docs/leader-election.md) -- [Tracing](docs/tracing.md) +After using multiresource controllers/retrievers, we though that we don't need a multiresource controller, this is not necesary becase: -The starting point would be to check the [concepts](docs/concepts.md) and then continue with the controller and operator tutorials. +- Adds complexity. +- Adds corner cases, this translates in bugs, e.g + - Internal object cache based on IDs of `{namespace}/{name}` scheme (ignoring types). + - Receiving a deletion watch event of one type removes the other type object with the same name from the cache (service and deployment have same ns and same name). + - The different resources that share name and ns, will be only process one of the types (sometimes is useful, others adds bugs and corner cases). +- An error on one of the retrieval types stops all the controller handling process and not only the one based on that type. +- Programatically speaking, you can reuse the `Handler` in multiple controllers. -## Who is using kooper +The solution to these problems, is to embrace simplicity once again, and mainly is creating multiple controllers using the same `Handler`, each controller with a different `ListerWatcher`. The `Handler` API is easy enough to reuse it across multiple controllers, check an [example][multiresource-example]. Also, this comes with extra benefits: -- [redis-operator](https://github.com/spotahome/redis-operator) -- [node-labeler-operator](https://github.com/barpilot/node-labeler-operator) -- [source-ranges-controller](https://github.com/jeffersongirao/source-ranges-controller) +- Different controller interval depending on the type (fast changing secondary objects can reconcile faster than the primary one, or viceversa). +- Wrap the controller handler with a middlewre only for a particular type. +- One of the type retrieval fails, the other type controller continues working (running in degradation mode). +- Flexibility, e.g leader election for the primary type, no leader election for the secondary type. +- Controller config has a handy flag to disable resync (`DisableResync`), sometimes this can be useful on secondary resources (only act on changes). [travis-image]: https://travis-ci.org/spotahome/kooper.svg?branch=master [travis-url]: https://travis-ci.org/spotahome/kooper @@ -182,7 +194,15 @@ The starting point would be to check the [concepts](docs/concepts.md) and then c [goreport-url]: https://goreportcard.com/report/github.com/spotahome/kooper [godoc-image]: https://godoc.org/github.com/spotahome/kooper?status.svg [godoc-url]: https://godoc.org/github.com/spotahome/kooper -[dependency-example]: https://github.com/slok/kooper-as-dependency -[dep-project]: https://github.com/golang/dep -[opentracing-url]: http://opentracing.io/ +[examples]: examples/ [grafana-dashboard]: https://grafana.com/dashboards/7082 +[controllers]: https://kubernetes.io/docs/concepts/architecture/controller/ +[kubebuilder]: https://github.com/kubernetes-sigs/kubebuilder +[operator-framework]: https://github.com/operator-framework +[kubewebhook]: https://github.com/slok/kubewebhook +[kube-code-generator]: https://github.com/slok/kube-code-generator +[owner-ref]: https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#owners-and-dependents +[finalizers]: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#finalizers +[finalizer-example]: examples/pod-terminator-operator/operator/operator.go +[multiresource-example]: examples/multi-resource-controller +[ci]: https://github.com/spotahome/kooper/actions diff --git a/client/crd/crd.go b/client/crd/crd.go deleted file mode 100644 index 7c427e8b..00000000 --- a/client/crd/crd.go +++ /dev/null @@ -1,234 +0,0 @@ -package crd - -import ( - "fmt" - "time" - - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - apiextensionscli "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kubeversion "k8s.io/apimachinery/pkg/util/version" - - "github.com/spotahome/kooper/log" - wraptime "github.com/spotahome/kooper/wrapper/time" -) - -const ( - checkCRDInterval = 2 * time.Second - crdReadyTimeout = 3 * time.Minute -) - -var ( - clusterMinVersion = kubeversion.MustParseGeneric("v1.7.0") - defCategories = []string{"all", "kooper"} -) - -// Scope is the scope of a CRD. -type Scope = apiextensionsv1beta1.ResourceScope - -const ( - // ClusterScoped represents a type of a cluster scoped CRD. - ClusterScoped = apiextensionsv1beta1.ClusterScoped - // NamespaceScoped represents a type of a namespaced scoped CRD. - NamespaceScoped = apiextensionsv1beta1.NamespaceScoped -) - -// Conf is the configuration required to create a CRD -type Conf struct { - // Kind is the kind of the CRD. - Kind string - // NamePlural is the plural name of the CRD (in most cases the plural of Kind). - NamePlural string - // ShortNames are short names of the CRD. It must be all lowercase. - ShortNames []string - // Group is the group of the CRD. - Group string - // Version is the version of the CRD. - Version string - // Scope is the scode of the CRD (cluster scoped or namespace scoped). - Scope Scope - // Categories is a way of grouping multiple resources (example `kubectl get all`), - // Kooper adds the CRD to `all` and `kooper` categories(apart from the described in Caregories). - Categories []string - // EnableStatus will enable the Status subresource on the CRD. This is feature - // entered in v1.10 with the CRD subresources. - // By default is disabled. - EnableStatusSubresource bool - // EnableScaleSubresource by default will be nil and means disabled, if - // the object is present it will set this scale configuration to the subresource. - EnableScaleSubresource *apiextensionsv1beta1.CustomResourceSubresourceScale -} - -func (c *Conf) getName() string { - return fmt.Sprintf("%s.%s", c.NamePlural, c.Group) -} - -// Interface is the CRD client that knows how to interact with k8s to manage them. -type Interface interface { - // EnsureCreated will ensure the the CRD is present, this also means that - // apart from creating the CRD if is not present it will wait until is - // ready, this is a blocking operation and will return an error if timesout - // waiting. - EnsurePresent(conf Conf) error - // WaitToBePresent will wait until the CRD is present, it will check if - // is present at regular intervals until it timesout, in case of timeout - // will return an error. - WaitToBePresent(name string, timeout time.Duration) error - // Delete will delete the CRD. - Delete(name string) error -} - -// Client is the CRD client implementation using API calls to kubernetes. -type Client struct { - aeClient apiextensionscli.Interface - logger log.Logger - time wraptime.Time // Use a time wrapper so we can control the time on our tests. -} - -// NewClient returns a new CRD client. -func NewClient(aeClient apiextensionscli.Interface, logger log.Logger) *Client { - return NewCustomClient(aeClient, wraptime.Base, logger) -} - -// NewCustomClient returns a new CRD client letting you set all the required parameters -func NewCustomClient(aeClient apiextensionscli.Interface, time wraptime.Time, logger log.Logger) *Client { - return &Client{ - aeClient: aeClient, - logger: logger, - time: time, - } -} - -// EnsurePresent satisfies crd.Interface. -func (c *Client) EnsurePresent(conf Conf) error { - if err := c.validClusterForCRDs(); err != nil { - return err - } - - // Get the generated name of the CRD. - crdName := conf.getName() - - // Create subresources - subres := c.createSubresources(conf) - - crd := &apiextensionsv1beta1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ - Group: conf.Group, - Version: conf.Version, - Scope: conf.Scope, - Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Plural: conf.NamePlural, - Kind: conf.Kind, - ShortNames: conf.ShortNames, - Categories: c.addDefaultCaregories(conf.Categories), - }, - Subresources: subres, - }, - } - - _, err := c.aeClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd) - if err != nil { - if !errors.IsAlreadyExists(err) { - return fmt.Errorf("error creating crd %s: %s", crdName, err) - } - return nil - } - - c.logger.Infof("crd %s created, waiting to be ready...", crdName) - if err := c.WaitToBePresent(crdName, crdReadyTimeout); err != nil { - return err - } - c.logger.Infof("crd %s ready", crdName) - - return nil -} - -func (c *Client) createSubresources(conf Conf) *apiextensionsv1beta1.CustomResourceSubresources { - if !conf.EnableStatusSubresource && conf.EnableScaleSubresource == nil { - return nil - } - - sr := &apiextensionsv1beta1.CustomResourceSubresources{} - - if conf.EnableStatusSubresource { - sr.Status = &apiextensionsv1beta1.CustomResourceSubresourceStatus{} - } - - if conf.EnableScaleSubresource != nil { - sr.Scale = conf.EnableScaleSubresource - } - - return sr -} - -// WaitToBePresent satisfies crd.Interface. -func (c *Client) WaitToBePresent(name string, timeout time.Duration) error { - if err := c.validClusterForCRDs(); err != nil { - return err - } - - tout := c.time.After(timeout) - t := c.time.NewTicker(checkCRDInterval) - - for { - select { - case <-t.C: - _, err := c.aeClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{}) - // Is present, finish. - if err == nil { - return nil - } - case <-tout: - return fmt.Errorf("timeout waiting for CRD") - } - } -} - -// Delete satisfies crd.Interface. -func (c *Client) Delete(name string) error { - if err := c.validClusterForCRDs(); err != nil { - return err - } - - return c.aeClient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(name, &metav1.DeleteOptions{}) -} - -// validClusterForCRDs returns nil if cluster is ok to be used for CRDs, otherwise error. -func (c *Client) validClusterForCRDs() error { - // Check cluster version. - v, err := c.aeClient.Discovery().ServerVersion() - if err != nil { - return err - } - parsedV, err := kubeversion.ParseGeneric(v.GitVersion) - if err != nil { - return err - } - - if parsedV.LessThan(clusterMinVersion) { - return fmt.Errorf("not a valid cluster version for CRDs (required >=1.7)") - } - - return nil -} - -// addAllCaregory adds the `all` category if isn't present -func (c *Client) addDefaultCaregories(categories []string) []string { - currentCats := make(map[string]bool) - for _, ca := range categories { - currentCats[ca] = true - } - - // Add default categories if required. - for _, ca := range defCategories { - if _, ok := currentCats[ca]; !ok { - categories = append(categories, ca) - } - } - - return categories -} diff --git a/client/crd/crd_test.go b/client/crd/crd_test.go deleted file mode 100644 index 47524037..00000000 --- a/client/crd/crd_test.go +++ /dev/null @@ -1,309 +0,0 @@ -package crd_test - -import ( - "errors" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - apiextensionscli "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" - kubeerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/version" - fakediscovery "k8s.io/client-go/discovery/fake" - kubetesting "k8s.io/client-go/testing" - - "github.com/spotahome/kooper/client/crd" - "github.com/spotahome/kooper/log" - mtime "github.com/spotahome/kooper/mocks/wrapper/time" -) - -var ( - crdGroup = schema.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions"} - goodClusterVersion = "v1.7" - badClusterVersion = "v1.6" - statusLabelSelector = ".status.labelSelector" -) - -// newClient returns a new mock client. -func newClient() *apiextensionscli.Clientset { - return newVersionedClusterClient(goodClusterVersion) -} - -// newVersionedClusterClient returns a new mock client with the cluster version set -func newVersionedClusterClient(clusterVersion string) *apiextensionscli.Clientset { - cli := apiextensionscli.NewSimpleClientset() - - // Fake cluster version. - fakeDiscovery, _ := cli.Discovery().(*fakediscovery.FakeDiscovery) - fakeDiscovery.FakedServerVersion = &version.Info{ - GitVersion: clusterVersion, - } - - // Use different fake action registry for CRD api calls client and discovery client. By default they - // share the same registry(testing.Fake) and we don't want to mix version check actions with the CRD - // actions. - cli.Fake = kubetesting.Fake{} - fakeDiscovery.Fake = &kubetesting.Fake{} - - return cli -} - -func newCRDGetAction(name string) kubetesting.GetActionImpl { - return kubetesting.NewGetAction(crdGroup, "", name) -} - -func newCRDCreateAction(crd *apiextensionsv1beta1.CustomResourceDefinition) kubetesting.CreateActionImpl { - return kubetesting.NewCreateAction(crdGroup, "", crd) -} - -func TestCRDEnsurePresent(t *testing.T) { - tests := []struct { - name string - clusterVersion string - crd crd.Conf - retErr error - expErr bool - expCalls []kubetesting.Action - }{ - { - name: "Creating a non existen CRD (using custom categories) should create a crd without error", - clusterVersion: goodClusterVersion, - crd: crd.Conf{ - Kind: "Test", - NamePlural: "tests", - ShortNames: []string{"tst"}, - Scope: crd.ClusterScoped, - Group: "toilettesting", - Version: "v99", - Categories: []string{ - "category1", - "categoryA", - }, - }, - retErr: nil, - expErr: false, - expCalls: []kubetesting.Action{ - newCRDCreateAction(&apiextensionsv1beta1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tests.toilettesting", - }, - Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ - Group: "toilettesting", - Version: "v99", - Scope: crd.ClusterScoped, - Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Plural: "tests", - Kind: "Test", - ShortNames: []string{"tst"}, - Categories: []string{"category1", "categoryA", "all", "kooper"}, - }, - }, - }), - newCRDGetAction("tests.toilettesting"), - }, - }, - { - name: "Creating a CRD that errors should return an error.", - clusterVersion: goodClusterVersion, - crd: crd.Conf{ - Kind: "Test", - NamePlural: "tests", - ShortNames: []string{"tst"}, - Scope: crd.ClusterScoped, - Group: "toilettesting", - Version: "v99", - }, - retErr: errors.New("wanted error"), - expErr: true, - expCalls: []kubetesting.Action{}, - }, - { - name: "Creating a CRD that exists shouldn't return an error.", - clusterVersion: goodClusterVersion, - crd: crd.Conf{ - Kind: "Test", - NamePlural: "tests", - ShortNames: []string{"tst"}, - Scope: crd.ClusterScoped, - Group: "toilettesting", - Version: "v99", - }, - expCalls: []kubetesting.Action{ - newCRDCreateAction(&apiextensionsv1beta1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tests.toilettesting", - }, - Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ - Group: "toilettesting", - Version: "v99", - Scope: crd.ClusterScoped, - Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Plural: "tests", - Kind: "Test", - ShortNames: []string{"tst"}, - Categories: []string{"all", "kooper"}, - }, - }, - }), - }, - retErr: kubeerrors.NewAlreadyExists(schema.GroupResource{}, ""), - expErr: false, - }, - { - name: "Creating a CRD with subresources active should create the CRD with the subresources set.", - clusterVersion: goodClusterVersion, - crd: crd.Conf{ - Kind: "Test", - NamePlural: "tests", - ShortNames: []string{"tst"}, - Scope: crd.ClusterScoped, - Group: "toilettesting", - Version: "v99", - EnableStatusSubresource: true, - EnableScaleSubresource: &apiextensionsv1beta1.CustomResourceSubresourceScale{ - SpecReplicasPath: ".spec.replicas", - StatusReplicasPath: ".status.replicas", - LabelSelectorPath: &statusLabelSelector, - }, - }, - expCalls: []kubetesting.Action{ - newCRDCreateAction(&apiextensionsv1beta1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tests.toilettesting", - }, - Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ - Group: "toilettesting", - Version: "v99", - Scope: crd.ClusterScoped, - Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Plural: "tests", - Kind: "Test", - ShortNames: []string{"tst"}, - Categories: []string{"all", "kooper"}, - }, - Subresources: &apiextensionsv1beta1.CustomResourceSubresources{ - Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{}, - Scale: &apiextensionsv1beta1.CustomResourceSubresourceScale{ - SpecReplicasPath: ".spec.replicas", - StatusReplicasPath: ".status.replicas", - LabelSelectorPath: &statusLabelSelector, - }, - }, - }, - }), - newCRDGetAction("tests.toilettesting"), - }, - retErr: nil, - expErr: false, - }, - { - name: "If the cluster version is not supported it should fail.", - clusterVersion: badClusterVersion, - crd: crd.Conf{}, - retErr: nil, - expErr: true, - expCalls: []kubetesting.Action{}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - - // Mock calls - cli := newVersionedClusterClient(test.clusterVersion) - cli.AddReactor("create", "customresourcedefinitions", func(action kubetesting.Action) (bool, runtime.Object, error) { - return true, nil, test.retErr - }) - cli.AddReactor("get", "customresourcedefinitions", func(action kubetesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - - crdCli := crd.NewClient(cli, log.Dummy) - err := crdCli.EnsurePresent(test.crd) - if test.expErr { - assert.Error(err) - } else if assert.NoError(err) { - assert.Equal(test.expCalls, cli.Actions()) - } - - }) - } -} - -func TestCRDWaitToBePresent(t *testing.T) { - never := 999999 * time.Hour - tests := []struct { - name string - clusterVersion string - crdName string - timeout time.Duration - interval time.Duration - readyAfter time.Duration - expErr bool - }{ - { - name: "If timeouts it should return an error", - clusterVersion: goodClusterVersion, - crdName: "test", - timeout: 1, - interval: never, - readyAfter: never, - expErr: true, - }, - { - name: "If the cluster version is not supported, it should fail", - clusterVersion: badClusterVersion, - crdName: "test", - timeout: 1, - interval: never, - readyAfter: never, - expErr: true, - }, - { - name: "After some time the CRD will be ready and won't timeout", - clusterVersion: goodClusterVersion, - crdName: "test", - timeout: 50 * time.Millisecond, - interval: 5 * time.Millisecond, - readyAfter: 25 * time.Millisecond, - expErr: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - - // Mock calls - mt := &mtime.Time{} - mt.On("After", mock.Anything).Once().Return(time.After(test.timeout)) - mt.On("NewTicker", mock.Anything).Once().Return(time.NewTicker(test.interval)) - cli := newVersionedClusterClient(test.clusterVersion) - start := time.Now() - cli.AddReactor("get", "customresourcedefinitions", func(action kubetesting.Action) (bool, runtime.Object, error) { - // Get how long we've been running and if it passed readyAfter - // our CRD is ready. - runningTime := time.Now().Sub(start) - if runningTime >= test.readyAfter { - return true, nil, nil - } - return true, nil, fmt.Errorf("wanted error") - - }) - - crdCli := crd.NewCustomClient(cli, mt, log.Dummy) - err := crdCli.WaitToBePresent(test.crdName, 0) - if test.expErr { - assert.Error(err) - } else { - assert.NoError(err) - } - }) - } -} diff --git a/controller/controller.go b/controller/controller.go new file mode 100644 index 00000000..36254f46 --- /dev/null +++ b/controller/controller.go @@ -0,0 +1,311 @@ +package controller + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + + "github.com/spotahome/kooper/v2/controller/leaderelection" + "github.com/spotahome/kooper/v2/log" +) + +var ( + // ErrControllerNotValid will be used when the controller has invalid configuration. + ErrControllerNotValid = errors.New("controller not valid") +) + +// Controller is the object that will implement the different kinds of controllers that will be running +// on the application. +type Controller interface { + // Run runs the controller, it receives a channel that when receiving a signal it will stop the controller, + // Run will block until it's stopped. + Run(stopper <-chan struct{}) error +} + +// Config is the controller configuration. +type Config struct { + // Handler is the controller handler. + Handler Handler + // Retriever is the controller retriever. + Retriever Retriever + // Leader elector will be used to use only one instance, if no set it will be + // leader election will be ignored + LeaderElector leaderelection.Runner + // MetricsRecorder will record the controller metrics. + MetricsRecorder MetricsRecorder + // Logger will log messages of the controller. + Logger log.Logger + + // name of the controller. + Name string + // ConcurrentWorkers is the number of concurrent workers the controller will have running processing events. + ConcurrentWorkers int + // ResyncInterval is the interval the controller will process all the selected resources. + ResyncInterval time.Duration + // ProcessingJobRetries is the number of times the job will try to reprocess the event before returning a real error. + ProcessingJobRetries int + // DisableResync will disable resyncing, if disabled the controller only will react on event updates and resync + // all when it runs for the first time. + // This is useful for secondary resource controllers (e.g pod controller of a primary controller based on deployments). + DisableResync bool +} + +func (c *Config) setDefaults() error { + if c.Name == "" { + return fmt.Errorf("a controller name is required") + } + + if c.Handler == nil { + return fmt.Errorf("a handler is required") + } + + if c.Retriever == nil { + return fmt.Errorf("a retriever is required") + } + + if c.Logger == nil { + c.Logger = log.NewStd(false) + c.Logger.Warningf("no logger specified, fallback to default logger, to disable logging use a explicit Noop logger") + } + c.Logger = c.Logger.WithKV(log.KV{ + "service": "kooper.controller", + "controller-id": c.Name, + }) + + if c.MetricsRecorder == nil { + c.MetricsRecorder = DummyMetricsRecorder + c.Logger.Warningf("no metrics recorder specified, disabling metrics") + } + + if c.ConcurrentWorkers <= 0 { + c.ConcurrentWorkers = 3 + } + + if c.ResyncInterval <= 0 { + c.ResyncInterval = 3 * time.Minute + } + + if c.DisableResync { + c.ResyncInterval = 0 // 0 == resync disabled. + } + + if c.ProcessingJobRetries < 0 { + c.ProcessingJobRetries = 0 + } + + return nil +} + +// generic controller is a controller that can be used to create different kind of controllers. +type generic struct { + queue blockingQueue // queue will have the jobs that the controller will get and send to handlers. + informer cache.SharedIndexInformer // informer will notify be inform us about resource changes. + processor processor // processor will call the user handler (logic). + + running bool + runningMu sync.Mutex + cfg Config + metrics MetricsRecorder + leRunner leaderelection.Runner + logger log.Logger +} + +func listerWatcherFromRetriever(ret Retriever) cache.ListerWatcher { + // TODO(slok): pass context when Kubernetes updates its ListerWatchers ¯\_(ツ)_/¯. + return &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return ret.List(context.TODO(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return ret.Watch(context.TODO(), options) + }, + } +} + +// New creates a new controller that can be configured using the cfg parameter. +func New(cfg *Config) (Controller, error) { + // Sets the required default configuration. + err := cfg.setDefaults() + if err != nil { + return nil, fmt.Errorf("could no create controller: %w: %v", ErrControllerNotValid, err) + } + + // Create the queue that will have our received job changes. + queue := newRateLimitingBlockingQueue( + cfg.ProcessingJobRetries, + workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), + ) + queue = newMetricsBlockingQueue( + cfg.Name, + cfg.MetricsRecorder, + queue, + cfg.Logger, + ) + + // store is the internal cache where objects will be store. + store := cache.Indexers{} + lw := listerWatcherFromRetriever(cfg.Retriever) + informer := cache.NewSharedIndexInformer(lw, nil, cfg.ResyncInterval, store) + + // Set up our informer event handler. + // Objects are already in our local store. Add only keys/jobs on the queue so they can re processed + // afterwards. + informer.AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + key, err := cache.MetaNamespaceKeyFunc(obj) + if err != nil { + cfg.Logger.Warningf("could not add item from 'add' event to queue: %s", err) + return + } + queue.Add(context.TODO(), key) + }, + UpdateFunc: func(_ interface{}, new interface{}) { + key, err := cache.MetaNamespaceKeyFunc(new) + if err != nil { + cfg.Logger.Warningf("could not add item from 'update' event to queue: %s", err) + return + } + queue.Add(context.TODO(), key) + }, + DeleteFunc: func(obj interface{}) { + key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) + if err != nil { + cfg.Logger.Warningf("could not add item from 'delete' event to queue: %s", err) + return + } + queue.Add(context.TODO(), key) + }, + }, cfg.ResyncInterval) + + // Create processing chain: processor(+middlewares) -> handler(+middlewares). + processor := newIndexerProcessor(informer.GetIndexer(), cfg.Handler) + if cfg.ProcessingJobRetries > 0 { + processor = newRetryProcessor(cfg.Name, cfg.ProcessingJobRetries, queue, cfg.Logger, processor) + } + processor = newMetricsProcessor(cfg.Name, cfg.MetricsRecorder, processor) + + // Create our generic controller object. + return &generic{ + queue: queue, + informer: informer, + metrics: cfg.MetricsRecorder, + processor: processor, + leRunner: cfg.LeaderElector, + cfg: *cfg, + logger: cfg.Logger, + }, nil +} + +func (g *generic) isRunning() bool { + g.runningMu.Lock() + defer g.runningMu.Unlock() + return g.running +} + +func (g *generic) setRunning(running bool) { + g.runningMu.Lock() + defer g.runningMu.Unlock() + g.running = running +} + +// Run will run the controller. +func (g *generic) Run(stopC <-chan struct{}) error { + // Check if leader election is required. + if g.leRunner != nil { + return g.leRunner.Run(func() error { + return g.run(stopC) + }) + } + + return g.run(stopC) +} + +// run is the real run of the controller. +func (g *generic) run(stopC <-chan struct{}) error { + if g.isRunning() { + return fmt.Errorf("controller already running") + } + + g.logger.Infof("starting controller") + // Set state of controller. + g.setRunning(true) + defer g.setRunning(false) + + // Shutdown when Run is stopped so we can process the last items and the queue doesn't + // accept more jobs. + defer g.queue.ShutDown(context.TODO()) + + // Run the informer so it starts listening to resource events. + go g.informer.Run(stopC) + + // Wait until our store, jobs... stuff is synced (first list on resource, resources on store and jobs on queue). + if !cache.WaitForCacheSync(stopC, g.informer.HasSynced) { + return fmt.Errorf("timed out waiting for caches to sync") + } + + // Start our resource processing worker, if finishes then restart the worker. The workers should + // not end. + for i := 0; i < g.cfg.ConcurrentWorkers; i++ { + go func() { + wait.Until(g.runWorker, time.Second, stopC) + }() + } + + // Until will be running our workers in a continuous way (and re run if they fail). But + // when stop signal is received we must stop. + <-stopC + g.logger.Infof("stopping controller") + + return nil +} + +// runWorker will start a processing loop on event queue. +func (g *generic) runWorker() { + for { + // Process next queue job, if needs to stop processing it will return true. + if g.processNextJob() { + break + } + } +} + +// processNextJob job will process the next job of the queue job and returns if +// it needs to stop processing. +// +// If the queue has been closed then it will end the processing. +func (g *generic) processNextJob() bool { + ctx := context.Background() + + // Get next job. + nextJob, exit := g.queue.Get(ctx) + if exit { + return true + } + + defer g.queue.Done(ctx, nextJob) + key := nextJob.(string) + + // Process the job. + err := g.processor.Process(ctx, key) + + logger := g.logger.WithKV(log.KV{"object-key": key}) + switch { + case err == nil: + logger.Debugf("object processed") + case errors.Is(err, errRequeued): + logger.Warningf("error on object processing, retrying: %v", err) + default: + logger.Errorf("error on object processing: %v", err) + } + + return false +} diff --git a/controller/controller_test.go b/controller/controller_test.go new file mode 100644 index 00000000..16c8f427 --- /dev/null +++ b/controller/controller_test.go @@ -0,0 +1,322 @@ +package controller_test + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + kubetesting "k8s.io/client-go/testing" + "k8s.io/client-go/tools/cache" + + "github.com/spotahome/kooper/v2/controller" + "github.com/spotahome/kooper/v2/controller/leaderelection" + "github.com/spotahome/kooper/v2/log" + mcontroller "github.com/spotahome/kooper/v2/mocks/controller" +) + +// NewNamespace returns a Namespace retriever. +func newNamespaceRetriever(client kubernetes.Interface) controller.Retriever { + return controller.MustRetrieverFromListerWatcher(&cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return client.CoreV1().Namespaces().List(options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return client.CoreV1().Namespaces().Watch(options) + }, + }) +} + +func onKubeClientListNamespaceReturn(client *fake.Clientset, nss *corev1.NamespaceList) { + client.AddReactor("list", "namespaces", func(action kubetesting.Action) (bool, runtime.Object, error) { + return true, nss, nil + }) +} + +func createNamespaceList(prefix string, q int) (*corev1.NamespaceList, []*corev1.Namespace) { + nss := []*corev1.Namespace{} + nsl := &corev1.NamespaceList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "1", + }, + Items: []corev1.Namespace{}, + } + + for i := 0; i < q; i++ { + nsName := fmt.Sprintf("%s-%d", prefix, i) + ns := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsName, + ResourceVersion: fmt.Sprintf("%d", i), + }, + } + + nsl.Items = append(nsl.Items, ns) + nss = append(nss, &ns) + } + + return nsl, nss +} + +func TestGenericControllerHandle(t *testing.T) { + nsList, expNSAdds := createNamespaceList("testing", 10) + + tests := []struct { + name string + nsList *corev1.NamespaceList + expNSAdds []*corev1.Namespace + }{ + { + name: "Listing multiple namespaces should execute the handling for every namespace on list.", + nsList: nsList, + expNSAdds: expNSAdds, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + controllerStopperC := make(chan struct{}) + resultC := make(chan error) + + // Mocks kubernetes client. + mc := &fake.Clientset{} + onKubeClientListNamespaceReturn(mc, test.nsList) + + // Mock our handler and set expects. + callHandling := 0 // used to track the number of calls. + mh := &mcontroller.Handler{} + + var mu sync.Mutex + for _, ns := range test.expNSAdds { + mh.On("Handle", mock.Anything, ns).Once().Return(nil).Run(func(args mock.Arguments) { + mu.Lock() + defer mu.Unlock() + callHandling++ + + // Check last call, if is the last call expected then stop the controller so + // we can assert the expectations of the calls and finish the test. + if callHandling == len(test.expNSAdds) { + close(controllerStopperC) + } + }) + } + + c, err := controller.New(&controller.Config{ + Name: "test", + Handler: mh, + Retriever: newNamespaceRetriever(mc), + Logger: log.Dummy, + }) + require.NoError(err) + + // Run Controller in background. + go func() { + resultC <- c.Run(controllerStopperC) + }() + + // Wait for different results. If no result means error failure. + select { + case err := <-resultC: + if assert.NoError(err) { + // Check handles from the controller. + mh.AssertExpectations(t) + } + case <-time.After(1 * time.Second): + assert.Fail("timeout waiting for controller handling, this could mean the controller is not receiving resources") + + } + }) + } +} + +func TestGenericControllerErrorRetries(t *testing.T) { + nsList, _ := createNamespaceList("testing", 11) + + tests := []struct { + name string + nsList *corev1.NamespaceList + retryNumber int + }{ + { + name: "Retrying N resources with M retries and error on all should be 1 + M processing calls per resource (N+N*M event processing calls).", + nsList: nsList, + retryNumber: 3, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + controllerStopperC := make(chan struct{}) + resultC := make(chan error) + + // Mocks kubernetes client. + mc := &fake.Clientset{} + // Populate cache so we ensure deletes are correctly delivered. + onKubeClientListNamespaceReturn(mc, nsList) + + // Mock our handler and set expects. + totalCalls := len(test.nsList.Items) + len(test.nsList.Items)*test.retryNumber + mh := &mcontroller.Handler{} + err := fmt.Errorf("wanted error") + + // Expect all the retries + var mu sync.Mutex + for range test.nsList.Items { + callsPerNS := test.retryNumber + 1 // initial call + retries. + mh.On("Handle", mock.Anything, mock.Anything).Return(err).Times(callsPerNS).Run(func(args mock.Arguments) { + mu.Lock() + defer mu.Unlock() + totalCalls-- + // Check last call, if is the last call expected then stop the controller so + // we can assert the expectations of the calls and finish the test. + if totalCalls <= 0 { + close(controllerStopperC) + } + }) + } + + c, err := controller.New(&controller.Config{ + Name: "test", + Handler: mh, + Retriever: newNamespaceRetriever(mc), + ProcessingJobRetries: test.retryNumber, + Logger: log.Dummy, + }) + require.NoError(err) + + // Run Controller in background. + go func() { + resultC <- c.Run(controllerStopperC) + }() + + // Wait for different results. If no result means error failure. + select { + case err := <-resultC: + if assert.NoError(err) { + // Check handles from the controller. + mh.AssertExpectations(t) + } + case <-time.After(1 * time.Second): + assert.Fail("timeout waiting for controller handling, this could mean the controller is not receiving resources") + } + }) + } +} + +func TestGenericControllerWithLeaderElection(t *testing.T) { + nsList, _ := createNamespaceList("testing", 5) + + tests := []struct { + name string + nsList *corev1.NamespaceList + retryNumber int + }{ + { + name: "", + nsList: nsList, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + controllerStopperC := make(chan struct{}) + resultC := make(chan error) + + // Mocks kubernetes client. + mc := fake.NewSimpleClientset(nsList) + + // Mock our handler and set expects. + mh1 := &mcontroller.Handler{} + mh2 := &mcontroller.Handler{} + mh3 := &mcontroller.Handler{} + + // Expect the calls on the lead (mh1) and no calls on the other ones. + totalCalls := len(test.nsList.Items) + mh1.On("Handle", mock.Anything, mock.Anything).Return(nil).Times(totalCalls).Run(func(args mock.Arguments) { + totalCalls-- + // Check last call, if is the last call expected then stop the controller so + // we can assert the expectations of the calls and finish the test. + if totalCalls <= 0 { + close(controllerStopperC) + } + }) + + nsret := newNamespaceRetriever(mc) + + // Leader election service. + rlCfg := &leaderelection.LockConfig{ + LeaseDuration: 9999 * time.Second, + RenewDeadline: 9998 * time.Second, + RetryPeriod: 500 * time.Second, + } + lesvc1, _ := leaderelection.New("test", "default", rlCfg, mc, log.Dummy) + lesvc2, _ := leaderelection.New("test", "default", rlCfg, mc, log.Dummy) + lesvc3, _ := leaderelection.New("test", "default", rlCfg, mc, log.Dummy) + + c1, err := controller.New(&controller.Config{ + Name: "test1", + Handler: mh1, + Retriever: nsret, + LeaderElector: lesvc1, + ProcessingJobRetries: test.retryNumber, + Logger: log.Dummy, + }) + require.NoError(err) + + c2, err := controller.New(&controller.Config{ + Name: "test2", + Handler: mh2, + Retriever: nsret, + LeaderElector: lesvc2, + ProcessingJobRetries: test.retryNumber, + Logger: log.Dummy, + }) + require.NoError(err) + + c3, err := controller.New(&controller.Config{ + Name: "test3", + Handler: mh3, + Retriever: nsret, + LeaderElector: lesvc3, + ProcessingJobRetries: test.retryNumber, + Logger: log.Dummy, + }) + require.NoError(err) + + // Run multiple controller in background. + go func() { resultC <- c1.Run(controllerStopperC) }() + // Let the first controller became the leader. + time.Sleep(200 * time.Microsecond) + go func() { resultC <- c2.Run(controllerStopperC) }() + go func() { resultC <- c3.Run(controllerStopperC) }() + + // Wait for different results. If no result means error failure. + select { + case err := <-resultC: + if assert.NoError(err) { + // Check handles from the controller. + mh1.AssertExpectations(t) + mh2.AssertExpectations(t) + mh3.AssertExpectations(t) + } + case <-time.After(1 * time.Second): + assert.Fail("timeout waiting for controller handling, this could mean the controller is not receiving resources") + } + }) + } +} diff --git a/operator/controller/doc.go b/controller/doc.go similarity index 53% rename from operator/controller/doc.go rename to controller/doc.go index 97c6ef45..ebcdb79a 100644 --- a/operator/controller/doc.go +++ b/controller/doc.go @@ -1,2 +1,2 @@ // Package controller contains implementation and defition to create kubernetes controllers. -package controller // import "github.com/spotahome/kooper/operator/controller" +package controller // import "github.com/spotahome/kooper/v2/operator/controller" diff --git a/controller/handler.go b/controller/handler.go new file mode 100644 index 00000000..5cf7abe7 --- /dev/null +++ b/controller/handler.go @@ -0,0 +1,24 @@ +package controller + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" +) + +// Handler knows how to handle the received resources from a kubernetes cluster. +type Handler interface { + Handle(context.Context, runtime.Object) error +} + +// HandlerFunc knows how to handle resource adds. +type HandlerFunc func(context.Context, runtime.Object) error + +// Handle satisfies controller.Handler interface. +func (h HandlerFunc) Handle(ctx context.Context, obj runtime.Object) error { + if h == nil { + return fmt.Errorf("handle func is required") + } + return h(ctx, obj) +} diff --git a/operator/controller/leaderelection/leaderelection.go b/controller/leaderelection/leaderelection.go similarity index 96% rename from operator/controller/leaderelection/leaderelection.go rename to controller/leaderelection/leaderelection.go index 54199bd9..61f00b70 100644 --- a/operator/controller/leaderelection/leaderelection.go +++ b/controller/leaderelection/leaderelection.go @@ -14,7 +14,7 @@ import ( "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/client-go/tools/record" - "github.com/spotahome/kooper/log" + "github.com/spotahome/kooper/v2/log" ) const ( @@ -74,7 +74,10 @@ func New(key, namespace string, lockCfg *LockConfig, k8scli kubernetes.Interface key: key, namespace: namespace, k8scli: k8scli, - logger: logger, + logger: logger.WithKV(log.KV{ + "source-service": "kooper/leader-election", + "leader-election-id": fmt.Sprintf("%s/%s", namespace, key), + }), } if err := r.validate(); err != nil { diff --git a/controller/metrics.go b/controller/metrics.go new file mode 100644 index 00000000..8b2dc341 --- /dev/null +++ b/controller/metrics.go @@ -0,0 +1,26 @@ +package controller + +import ( + "context" + "time" +) + +// MetricsRecorder knows how to record metrics of a controller. +type MetricsRecorder interface { + // IncResourceEvent increments in one the metric records of a queued event. + IncResourceEventQueued(ctx context.Context, controller string, isRequeue bool) + // ObserveResourceInQueueDuration measures how long takes to dequeue a queued object. If the object is already in queue + // it will be measured once, since the first time it was added to the queue. + ObserveResourceInQueueDuration(ctx context.Context, controller string, queuedAt time.Time) + // ObserveResourceProcessingDuration measures how long it takes to process a resources (handling). + ObserveResourceProcessingDuration(ctx context.Context, controller string, success bool, startProcessingAt time.Time) +} + +// DummyMetricsRecorder is a dummy metrics recorder. +var DummyMetricsRecorder = dummy(0) + +type dummy int + +func (dummy) IncResourceEventQueued(context.Context, string, bool) {} +func (dummy) ObserveResourceInQueueDuration(context.Context, string, time.Time) {} +func (dummy) ObserveResourceProcessingDuration(context.Context, string, bool, time.Time) {} diff --git a/controller/processor.go b/controller/processor.go new file mode 100644 index 00000000..390bfebd --- /dev/null +++ b/controller/processor.go @@ -0,0 +1,76 @@ +package controller + +import ( + "context" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/cache" + + "github.com/spotahome/kooper/v2/log" +) + +// processor knows how to process object keys. +type processor interface { + Process(ctx context.Context, key string) error +} + +// processorFunc a helper to create processors. +type processorFunc func(ctx context.Context, key string) error + +func (p processorFunc) Process(ctx context.Context, key string) error { return p(ctx, key) } + +// newIndexerProcessor returns a processor that processes a key that will get the kubernetes object +// from a cache called indexer were the kubernetes watch updates have been indexed and stored +// by the listerwatchers from the informers. +func newIndexerProcessor(indexer cache.Indexer, handler Handler) processor { + return processorFunc(func(ctx context.Context, key string) error { + // Get the object + obj, exists, err := indexer.GetByKey(key) + if err != nil { + return err + } + + if !exists { + return nil + } + + return handler.Handle(ctx, obj.(runtime.Object)) + }) +} + +var errRequeued = fmt.Errorf("requeued after receiving error") + +// newRetryProcessor returns a processor that will delegate the processing of a key to the +// received processor, in case the processing/handling of this key fails it will add the key +// again to a queue if it has retrys pending. +// +// If the processing errored and has been retried, it will return a `errRequeued` error. +func newRetryProcessor(name string, maxRetries int, queue blockingQueue, logger log.Logger, next processor) processor { + return processorFunc(func(ctx context.Context, key string) error { + err := next.Process(ctx, key) + if err != nil { + // Retry if possible. + requeueErr := queue.Requeue(ctx, key) + if requeueErr != nil { + return fmt.Errorf("could not retry: %s: %w", requeueErr, err) + } + logger.WithKV(log.KV{"object-key": key}).Warningf("item requeued due to processing error: %s", err) + return nil + } + + return nil + }) +} + +// newMetricsProcessor returns a processor that measures everything related with the processing logic. +func newMetricsProcessor(name string, mrec MetricsRecorder, next processor) processor { + return processorFunc(func(ctx context.Context, key string) (err error) { + defer func(t0 time.Time) { + mrec.ObserveResourceProcessingDuration(ctx, name, err == nil, t0) + }(time.Now()) + + return next.Process(ctx, key) + }) +} diff --git a/controller/queue.go b/controller/queue.go new file mode 100644 index 00000000..27d7005b --- /dev/null +++ b/controller/queue.go @@ -0,0 +1,144 @@ +package controller + +import ( + "context" + "fmt" + "sync" + "time" + + "k8s.io/client-go/util/workqueue" + + "github.com/spotahome/kooper/v2/log" +) + +// blockingQueue is a queue that any of its implementations should +// implement a blocking mechanism when the +type blockingQueue interface { + // Add will add an item to the queue. + Add(ctx context.Context, item interface{}) + // Requeue will add an item to the queue in a requeue mode. + // If doesn't accept requeueing or max requeue have been reached + // it will return an error. + Requeue(ctx context.Context, item interface{}) error + // Get is a blocking operation, if the last object usage has not been finished (`done`) + // being used it will block until this has been done. + Get(ctx context.Context) (item interface{}, shutdown bool) + // Done marks the item being used as done. + Done(ctx context.Context, item interface{}) + // ShutDown stops the queue from accepting new jobs + ShutDown(ctx context.Context) +} + +var ( + errMaxRetriesReached = fmt.Errorf("max retries reached") +) + +type rateLimitingBlockingQueue struct { + maxRetries int + queue workqueue.RateLimitingInterface +} + +func newRateLimitingBlockingQueue(maxRetries int, queue workqueue.RateLimitingInterface) blockingQueue { + return rateLimitingBlockingQueue{ + maxRetries: maxRetries, + queue: queue, + } +} + +func (r rateLimitingBlockingQueue) Add(_ context.Context, item interface{}) { + r.queue.Add(item) +} + +func (r rateLimitingBlockingQueue) Requeue(_ context.Context, item interface{}) error { + // If there was an error and we have retries pending then requeue. + if r.queue.NumRequeues(item) < r.maxRetries { + r.queue.AddRateLimited(item) + return nil + } + + r.queue.Forget(item) + return errMaxRetriesReached +} + +func (r rateLimitingBlockingQueue) Get(_ context.Context) (item interface{}, shutdown bool) { + return r.queue.Get() +} + +func (r rateLimitingBlockingQueue) Done(_ context.Context, item interface{}) { + r.queue.Done(item) +} + +func (r rateLimitingBlockingQueue) ShutDown(_ context.Context) { + r.queue.ShutDown() +} + +// metricsQueue is a wrapper for a metrics measured queue. +type metricsBlockingQueue struct { + mu sync.Mutex + name string + mrec MetricsRecorder + itemsQueuedAt map[interface{}]time.Time + logger log.Logger + queue blockingQueue +} + +func newMetricsBlockingQueue(name string, mrec MetricsRecorder, queue blockingQueue, logger log.Logger) blockingQueue { + return &metricsBlockingQueue{ + name: name, + mrec: mrec, + itemsQueuedAt: map[interface{}]time.Time{}, + logger: logger, + queue: queue, + } +} + +func (m *metricsBlockingQueue) Add(ctx context.Context, item interface{}) { + m.mu.Lock() + if _, ok := m.itemsQueuedAt[item]; !ok { + m.itemsQueuedAt[item] = time.Now() + } + m.mu.Unlock() + + m.mrec.IncResourceEventQueued(ctx, m.name, false) + m.queue.Add(ctx, item) +} + +func (m *metricsBlockingQueue) Requeue(ctx context.Context, item interface{}) error { + m.mu.Lock() + if _, ok := m.itemsQueuedAt[item]; !ok { + m.itemsQueuedAt[item] = time.Now() + } + m.mu.Unlock() + + m.mrec.IncResourceEventQueued(ctx, m.name, true) + return m.queue.Requeue(ctx, item) +} + +func (m *metricsBlockingQueue) Get(ctx context.Context) (interface{}, bool) { + // Here should get blocked, warning with the mutexes. + item, shutdown := m.queue.Get(ctx) + if shutdown { + return item, shutdown + } + + m.mu.Lock() + queuedAt, ok := m.itemsQueuedAt[item] + if ok { + m.mrec.ObserveResourceInQueueDuration(ctx, m.name, queuedAt) + delete(m.itemsQueuedAt, item) + } else { + m.logger.WithKV(log.KV{"object-key": item}). + Infof("could not measure item because item is not present on metricsMeasuredQueue.itemsQueuedAt map") + } + m.mu.Unlock() + + return item, shutdown +} + +func (m *metricsBlockingQueue) Done(ctx context.Context, item interface{}) { + m.queue.Done(ctx, item) +} + +func (m *metricsBlockingQueue) ShutDown(ctx context.Context) { + m.queue.ShutDown(ctx) +} diff --git a/controller/retrieve.go b/controller/retrieve.go new file mode 100644 index 00000000..f4b3c306 --- /dev/null +++ b/controller/retrieve.go @@ -0,0 +1,50 @@ +package controller + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" +) + +// Retriever is how a controller will retrieve the events on the resources from +// the APÎ server. +// +// A Retriever is bound to a single type. +type Retriever interface { + List(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) + Watch(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) +} + +type listerWatcherRetriever struct { + lw cache.ListerWatcher +} + +// RetrieverFromListerWatcher returns a Retriever from a Kubernetes client-go cache.ListerWatcher. +// If the received lister watcher is nil it will error. +func RetrieverFromListerWatcher(lw cache.ListerWatcher) (Retriever, error) { + if lw == nil { + return nil, fmt.Errorf("listerWatcher can't be nil") + } + return listerWatcherRetriever{lw: lw}, nil +} + +// MustRetrieverFromListerWatcher returns a Retriever from a Kubernetes client-go cache.ListerWatcher +// if there is an error it will panic. +func MustRetrieverFromListerWatcher(lw cache.ListerWatcher) Retriever { + r, err := RetrieverFromListerWatcher(lw) + if lw == nil { + panic(err) + } + return r +} + +func (l listerWatcherRetriever) List(_ context.Context, options metav1.ListOptions) (runtime.Object, error) { + return l.lw.List(options) +} +func (l listerWatcherRetriever) Watch(_ context.Context, options metav1.ListOptions) (watch.Interface, error) { + return l.lw.Watch(options) +} diff --git a/controller/retrieve_test.go b/controller/retrieve_test.go new file mode 100644 index 00000000..56d8e877 --- /dev/null +++ b/controller/retrieve_test.go @@ -0,0 +1,114 @@ +package controller_test + +import ( + "context" + "fmt" + "testing" + + "github.com/spotahome/kooper/v2/controller" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" +) + +var ( + testPodList = &corev1.PodList{ + Items: []corev1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "test1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "test2"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "test3"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "test4"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "test5"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "test6"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "test7"}}, + }, + } + testEventList = []watch.Event{ + {Type: watch.Added, Object: &testPodList.Items[0]}, + {Type: watch.Added, Object: &testPodList.Items[1]}, + {Type: watch.Added, Object: &testPodList.Items[2]}, + {Type: watch.Added, Object: &testPodList.Items[3]}, + {Type: watch.Added, Object: &testPodList.Items[4]}, + {Type: watch.Added, Object: &testPodList.Items[5]}, + {Type: watch.Added, Object: &testPodList.Items[6]}, + } +) + +func testPodListFunc(pl *corev1.PodList) cache.ListFunc { + return func(options metav1.ListOptions) (runtime.Object, error) { + return pl, nil + } +} + +func testEventWatchFunc(evs []watch.Event) cache.WatchFunc { + return func(options metav1.ListOptions) (watch.Interface, error) { + cg := make(chan watch.Event) + go func() { + for _, ev := range evs { + cg <- ev + } + close(cg) + }() + + return watch.NewProxyWatcher(cg), nil + } +} + +func TestRetrieverFromListerWatcher(t *testing.T) { + tests := map[string]struct { + listerWatcher cache.ListerWatcher + expList runtime.Object + expListErr bool + expWatch []watch.Event + expWatchErr bool + }{ + "A List error or a watch error should be propagated to the upper layer": { + listerWatcher: &cache.ListWatch{ + ListFunc: func(_ metav1.ListOptions) (runtime.Object, error) { return nil, fmt.Errorf("wanted error") }, + WatchFunc: func(_ metav1.ListOptions) (watch.Interface, error) { return nil, fmt.Errorf("wanted error") }, + }, + expListErr: true, + expWatchErr: true, + }, + + "List and watch should call the Kubernetes go clients lister watcher correctly.": { + listerWatcher: &cache.ListWatch{ + ListFunc: testPodListFunc(testPodList), + WatchFunc: testEventWatchFunc(testEventList), + }, + expList: testPodList, + expWatch: testEventList, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + ret := controller.MustRetrieverFromListerWatcher(test.listerWatcher) + + // Test list. + objs, err := ret.List(context.TODO(), metav1.ListOptions{}) + if test.expListErr { + assert.Error(err) + } else if assert.NoError(err) { + assert.Equal(test.expList, objs) + } + + // Test watch. + w, err := ret.Watch(context.TODO(), metav1.ListOptions{}) + evs := []watch.Event{} + if test.expWatchErr { + assert.Error(err) + } else if assert.NoError(err) { + for ev := range w.ResultChan() { + evs = append(evs, ev) + } + assert.Equal(test.expWatch, evs) + } + }) + } +} diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index fd13e18d..90d4ef36 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -1,27 +1,35 @@ -FROM golang:1.13-alpine +FROM golang:1.14 -RUN apk --no-cache add \ - g++ \ - git +ARG GOLANGCI_LINT_VERSION="1.25.0" +ARG ostype=Linux -# Mock creator -RUN go get -u github.com/vektra/mockery/.../ +RUN apt-get update && apt-get install -y \ + git \ + bash \ + zip -# Create user + +RUN wget https://github.com/golangci/golangci-lint/releases/download/v${GOLANGCI_LINT_VERSION}/golangci-lint-${GOLANGCI_LINT_VERSION}-linux-amd64.tar.gz && \ + tar zxvf golangci-lint-${GOLANGCI_LINT_VERSION}-linux-amd64.tar.gz --strip 1 -C /usr/local/bin/ && \ + rm golangci-lint-${GOLANGCI_LINT_VERSION}-linux-amd64.tar.gz + +RUN go get -u github.com/vektra/mockery/... + +# Create user. ARG uid=1000 ARG gid=1000 -RUN addgroup -g $gid kooper && \ - adduser -D -u $uid -G kooper kooper +RUN bash -c 'if [ ${ostype} == Linux ]; then addgroup -gid $gid app; else addgroup app; fi && \ + adduser --disabled-password -uid $uid --ingroup app --gecos "" app && \ + chown app:app -R /go' # Fill go mod cache. RUN mkdir /tmp/cache COPY go.mod /tmp/cache COPY go.sum /tmp/cache +RUN chown app:app -R /tmp/cache +USER app RUN cd /tmp/cache && \ go mod download -RUN chown kooper:kooper -R /go -USER kooper - -WORKDIR /src \ No newline at end of file +WORKDIR /src diff --git a/docs/concepts.md b/docs/concepts.md deleted file mode 100644 index c32fc5ec..00000000 --- a/docs/concepts.md +++ /dev/null @@ -1,114 +0,0 @@ - -Start -===== - -Kooper motivation: Help creating Kubernetes operators and controllers by giving a framework, libraries or a set of tools (pick the name that you prefer). - -If you are new to kooper first get familiar with the basic concepts and design of this toolkit, after that you will be redirected to some simple operators and controllers explained so you can see what can you do with this toolkit. - -## Concepts - -These will be the pieces of the framework required to implement and know to create an operator. -The required pieces you need to implement and know to create an operator are: -* `controller`: Take actions to comply the needs of resources. -* `retriever`: Knows how to list, watch and create a void object. -* `CRD`: A retriever that knows how to Initialize. -* `Handler`: Will be called when were events in the resources. - -### Controller - -A Kubernetes controller is something that listens/watch the status of a Kubernetes resource and takes actions so it meets the state required by the resource. Example: - -I listen to a `replicaset` resource that wants N instances of X `pod`. My controller should take whatever action it considers so the cluster has N instances of X. In this case It will create N `pod` resources. - -In this framework it is defined as an interface [here](https://github.com/spotahome/kooper/tree/master/operator/controller) - -```go -package controller - -type Controller interface { - Run(stopper <-chan struct{}) error -} -``` - -### Retriever - -In kubernetes world the way of retrieving resources is listing or watching (streaming events) them. In Kubernetes client libraries they are called [listerwatchers](https://github.com/kubernetes/client-go/blob/1b825e3a786379cb2ae2edc98a39e9c8cd68ee3c/tools/cache/listwatch.go#L35-L41). They know how to list and watch a resource kind. - -In this framework we have the concept of `Retriever` It knows how to list, watch and create a void object -so we can know the kind of the Retriever by inspecting the object. It is defined [here](https://github.com/spotahome/kooper/tree/master/operator/retrieve) - -```go -type Retriever interface { - GetListerWatcher() cache.ListerWatcher - GetObject() runtime.Object -} -``` - -### CRD - -A `crd` is a Custom Resource Definition (the evolution of the TPRs). It's a Kubernetes resource that is not a Kubernetes base resource (`pod`, `deployment`, `secret`...). It's used so we (as Kubernetes users) can create our custom resources (in the end a manifest/spec/definition) and use new resource kinds inside the cluster. - -In this framework we have the concept of `CRD`. It's just a retriever that knows how to Initialize. -The Initialize exists because when you want to use it in a Kubernetes cluster you need to ensure that the CRD is previously present (registered). - -```go -type CRD interface { - retrieve.Retriever - Initialize() error -} -``` - -### Handler - -The Handler is where our operator/controller logic will be placed. In other words, every time the resource we are *listwatching* for its events (delete, create, changed) the handler will be called. - -```go -type Handler interface { - Add(context.Context, runtime.Object) error - Delete(context.Context, string) error -} -``` - -Maybe you have some doubts like... - -* Where is the update method? -In the Kuberntes world there is the concept of state and eventual consistency and reconciliation loops. In a few words, You don't need to know -if the resource is new or not, only that the state should be this and eventually that state will be real in the cluster. - -* What happens if it errors my handling? -The event will be requeued for a new handling in the future until it rate limits the maximum times allowed (if this isn't rate limited you could get stuck forever handling same resources) - -* A context as parameter? -The context can be ignored if you don't need it at all, but if tracing is active the context will have the parent span. - - -### Operator - -All the concepts described above are glued together and create an operator. An [operator](https://coreos.com/operators/) its a concept invented by the CoreOS devs that is basically a regular controller that automates operator (a person) actions, so we manage CRDs to represent resources that our controllers will manage them. Example: - -I need to deploy a Prometheus, set up configuration... Instead of doing this myself (as a human operator) I create a new resource kind called `Prometheus`, or in other words a CRD called `Prometheus` with different options and create a controller that knows how to set the required state for our Prometheus CRD (this means deploying, configuring, backups, updating... a Promethus instance.). - -This way we just automated repetitive stuff, remove toil, errors... - -In this framework the concept operator is: controller(s) + CRD(s) = Operator. In other words an Operator initializes one or more CRDs and the runs one or more controllers related to one or more CRDs previously initialized. - -```go -type Operator interface { - Initialize() error - controller.Controller -} -``` - -## Usage - -In the end when using this framework or toolkit you need to know the concepts, but you will not use -all of them directly. For example we provide ways of bootstraping controllers and operators with already implemented utils. - -## Next - -At this moment you have the basic knowledge or the pillars that sustain this framework/toolkit, the next -thing you need to know is how to create and run stuff built with this toolkit: - -* [Controller tutorial.](controller-tutorial.md) -* [Operator tutorial.](operator-tutorial.md) diff --git a/docs/controller-tutorial.md b/docs/controller-tutorial.md deleted file mode 100644 index f9858390..00000000 --- a/docs/controller-tutorial.md +++ /dev/null @@ -1,188 +0,0 @@ -Controller tutorial -=================== - -In this tutorial we will learn how to create a controller using kooper. Yes, I know what you are thinking, kooper is more an operator library... but an operator as we described in the [concepts](concepts.md) is a controller on steroids and controllers are also fully supported in Kooper. - -So... In this tutorial we will learn the pillars of the operator, the controller. The full controller is [here](https://github.com/spotahome/kooper/tree/master/examples/echo-pod-controller), we will go step by step but some of the code is *glue* or doesn't refer to kooper. - -Lets start! - -## 01 - Description. - -Our Controller will log all the add/delete events that occur to pods on a given namespace. Easy peasy... Lets call it `echo-pod-controller` (yes, very original). The full controller is in [examples/echo-pod-controller](https://github.com/spotahome/kooper/tree/master/examples/echo-pod-controller). - -### Structure. - -The structure of the controller is very simple. - -```bash -./examples/echo-pod-controller/ -├── cmd -│   ├── flags.go -│   └── main.go -├── controller -│   ├── config.go -│   ├── echo.go -│   └── retrieve.go -├── log -│   └── log.go -└── service - ├── service.go - └── service_test.go -``` - -From this structure the important paths are `controller` where all the controller stuff will be, this is, creation, initialization... - -And `service` our domain logic. - -The other ones are not so important for the tutorial, you should check the whole project to have in mind how is structured a full controller. `cmd` is the main program (flags, signal capturing, dependecy creation...). `log` is where our logger is. - -### Unit testing. - -Testing is important. As you see this project has very little unit tests. This is because of two things: - -One, the project is very simple. - -Two, you can trust Kubernetes and Kooper libraries, they are already tested, you don't need to test this, but you should test your domain logic (and if you want, main and glue code also). In this controller we just tested the service that has our domain logic (that is just a simple logging). - -## 02 - Echo service. - -First thigs first, we will implement our domain logic that doesn't know anything of our controller, in other words, our service will do the heavy stuff of the controller and what makes it special or different from other controllers. - -Our controller is a logger just that. - -```go -type Echo interface { - EchoObj(prefix string, obj runtime.Object) - EchoS(prefix string, s string) -} -``` - -We implemented that `Echo` service as `SimpleEcho` check it out on the [service file](https://github.com/spotahome/kooper/blob/master/examples/echo-pod-controller/service/service.go). - -Now is time to use Kooper and leverage all the controller stuff. - -## 03 - Controller configuration. - -We need to implement our controller configuration. The controller is simple, it will need a namespace to know what pods should log and also a (re)synchronization period where the controller will receive all the pods again (apart from real time events). - -[controller/config.go](https://github.com/spotahome/kooper/blob/master/examples/echo-pod-controller/controller/config.go) - -```go -type Config struct { - ResyncPeriod time.Duration - Namespace string -} -``` - -Simple. - -Note we don't have validation, but you could set a method on the `Config` object to validate the configuration. - -## 04 - Controller retriever. - -Like we state on the basics, the retriever is the way the controller knows how to listen to resource events, this is, **list** (initial get and resyncs) and **watch** (real time events). And also know what is the kind of the object is listening to. - -Our controller is for pods so our retriever will use the [Kubernetes client](https://github.com/kubernetes/client-go) to get the pods, for example: - - -```go -client.CoreV1().Pods(namespace).List(options) -``` - -Check the retriever [here](https://github.com/spotahome/kooper/blob/master/examples/echo-pod-controller/controller/retrieve.go) - - -## 05 - Controller handler. - -As the name says, the handler is the place where kooper controller/operator will call when it has an event regarding the resource is listening with the retriever, in our case pods. - -Handler will receive events on: - -* On the first iteration (when the controller starts) and on every resync (intervals) it will call as an `Add` so you get the full list of resources. -* On a resource deletion it will call `Delete`. -* On a resource update it will call `Add` - -At first can look odd that an update on a resource calls `Add`. But we are getting a desired and eventual state of a resource, so doesn't matter if is new or old and has been updated, the reality is that our resource is in this state at this moment and we need to take actions or check previously before taking actions (imagine if we send an email, if we don't do a check we could end up with thousand of emails...). - -In our case we don't bother to check if is new or old or if we have done something related with a previous event on the same resource. We just call our Echo Service. - - -[controller/echo.go](https://github.com/spotahome/kooper/blob/master/examples/echo-pod-controller/controller/echo.go) - -```go -type handler struct { - echoSrv service.Echo -} - -func (h *handler) Add(_ context.Context, obj runtime.Object) error { - h.echoSrv.EchoObj(addPrefix, obj) - return nil -} -func (h *handler) Delete(_ context.Context, s string) error { - h.echoSrv.EchoS(deletePrefix, s) - return nil -} -``` - -## 06 - Controller. - -We have all the pieces of the controller except the controller itself, but don't worry, Kooper gives you a controller implementation so you can glue all together and create a controller. - -This can be found in [controller/echo.go](https://github.com/spotahome/kooper/blob/master/examples/echo-pod-controller/controller/echo.go) (is the same file of the handler). - -We will go step by step: - -```go -type Controller struct { - controller.Controller - - config Config - logger log.Logger -} -``` - -Controller is our controller, it has a logger, a controller configuration(step 03), and a kooper controller that will have all the required stuff to run a controller. - -Our contructor starts by creating the dependencies to create `DefaultGeneric` kooper controller. - -```go -ret := NewPodRetrieve(config.Namespace, k8sCli) -``` - -This is our retriever (step 04), the kubernetes client that we pass to the retriever constructor is created on the main and passed to the controller constructor (where we are and create this retriever). - -```go -echoSrv := service.NewSimpleEcho(logger) -``` -We create our service (step 01), this is for the handler. - -```go -handler := &handler{echoSrv: echoSrv} -``` - -Then we create our simple handler that will have our service. - -And... finally we create the controller! - -```go -ctrl := controller.NewSequential(config.ResyncPeriod, handler, ret, nil, logger) -``` - -We are using a sequential controller constructor (`NewSequential`) from `"github.com/spotahome/kooper/operator/controller"` package. It receives a handler, a retriever, a logger and a resync period. - -Wow, that was easy :) - -## 07 - Finishing. - -After all these steps we have a controller, now just depends how the main is organized or where you start the controller. You can check how is initialized the kubernetes client on the exmaple's [main]((https://github.com/spotahome/kooper/blob/master/examples/echo-pod-controller/cmd/main.go)), call our controller constructor and run it. But mainly is this: - -```go -//... -ctrl, err := controller.New(cfg, k8sCli, logger) -//... -ctrl.Run(stopC) -``` - -The Run method receives a channel that when is closed all the controller stuff will be stopped. - diff --git a/docs/leader-election.md b/docs/leader-election.md index 483dd2cf..86181295 100644 --- a/docs/leader-election.md +++ b/docs/leader-election.md @@ -8,12 +8,12 @@ A controller can be run with multiple instances in HA and only one will be reall The default controllers don't run in leader election mode: -* `controller.NewSequential` -* `controller.NewConcurrent` +- `controller.NewSequential` +- `controller.NewConcurrent` To use the leader election you can use: -* `controller.New` +- `controller.New` These method accepts a [`leaderelection.Runner`][leaderelection-src] service that manages the leader election of the controller, if this service is a `nil` object the controller will fallback to a regular controller mode. @@ -24,7 +24,7 @@ Lets take and example of creating using the default leader election service. ```golang import ( ... - "github.com/spotahome/kooper/operator/controller/leaderelection" + "github.com/spotahome/kooper/v2/operator/controller/leaderelection" ... ) @@ -40,7 +40,7 @@ Another example customizing the lock would be: ```golang import ( ... - "github.com/spotahome/kooper/operator/controller/leaderelection" + "github.com/spotahome/kooper/v2/operator/controller/leaderelection" ... ) @@ -109,11 +109,10 @@ docker run --name ctrl2 \ Now you can test disconnecting and connecting them using these commands and checking the results. -* `docker network disconnect bridge ctrl2` -* `docker network disconnect bridge ctrl1` -* `docker network connect bridge ctrl2` -* `docker network connect bridge ctrl1` - +- `docker network disconnect bridge ctrl2` +- `docker network disconnect bridge ctrl1` +- `docker network connect bridge ctrl2` +- `docker network connect bridge ctrl1` [leaderelection-src]: https://github.com/spotahome/kooper/tree/master/operator/controller/leaderelection -[leaderelection-example]: https://github.com/spotahome/kooper/tree/master/examples/leader-election-controller \ No newline at end of file +[leaderelection-example]: https://github.com/spotahome/kooper/tree/master/examples/leader-election-controller diff --git a/docs/logger.md b/docs/logger.md deleted file mode 100644 index 83408366..00000000 --- a/docs/logger.md +++ /dev/null @@ -1,42 +0,0 @@ -# Logger - -In kooper everything is pluggable, this is because it embraces dependency injection in all of its components, that's why you can plug your own logger, the only requirement is to implement the [`Logger`][logger-interface] interface. - -```golang -type Logger interface { - Infof(format string, args ...interface{}) - Warningf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) -} -``` - -Although it comes with some loggers by default. - -## Available loggers - -If you don't need a custom logger, you can pass - -* Dummy: doesn't log anything (mainly for the tests). -* Std: Uses default go logger (this logger is a global logger) - -## Use a logger in the controller - -```golang -import ( - "github.com/spotahome/kooper/log" - ... -) -... - -log := &log.Std{} - -... - -ctrl := controller.NewSequential(30*time.Second, hand, retr, m, log) -... -``` - -**Note: if you pass nil as the logger to the controller it will use `log.Std` logger by default** - - -[logger-interface]: https://github.com/spotahome/kooper/blob/master/log/log.go diff --git a/docs/metrics.md b/docs/metrics.md deleted file mode 100644 index 2ec1a4f3..00000000 --- a/docs/metrics.md +++ /dev/null @@ -1,79 +0,0 @@ -# Metrics - -Kooper comes with metrics support, this means that when you use kooper to bootstrap your operator or controller you have the possibility of instrumenting your controller for free. - -## Backends - -At this moment these are the supported backends: - -- Prometheus. - -## Custom backend - -ALthough Kooper supports by default some of the de-facto standard instrumenting backends, you could create your own backend, you just need to implement the provided [interface][metrics-interface] that is named as `Recorder`: - -```golang -type Recorder interface { - ... -} -``` - -## Measured metrics - -The measured metrics are: - -- Number of delete and add queued events (to be processed). -- Number of delete and add processed events. -- Number of delete and add processed events with an error. -- Duration of delete and add processed events. - -## How to use the recorder with the controller. - -When you create a controller you can pass the metrics recorder that you want. -**Note: If you pass a `nil` backend, it will not record any metric** - -If you want a full example, there is a controller [example][metrics-example] that uses the different metric backends - -### Prometheus - -Prometheus backend needs a prometheus [registerer][prometheus-registerer] and a namespace (a prefix for the metircs). - -For example: - -```golang -import ( - ... - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - ... -) - -... - - reg := prometheus.NewRegistry() - m := metrics.NewPrometheus(metricsPrefix, reg) - - ... - - ctrl := controller.NewSequential(30*time.Second, hand, retr, m, log) - - ... - - h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) - logger.Infof("serving metrics at %s", metricsAddr) - http.ListenAndServe(metricsAddr, h) - - return m -} -``` - -If you are using the default prometheus methods instead of a custom registry, you could get that from `prometheus.DefaultRegisterer` instead of creating a new registry `reg := prometheus.NewRegistry()` - -## Grafana dashboard (For Prometheus metrics) - -There is a grafana dashboard for your Kubernetes controllers. You can get it [here][grafana-dashboard] - -[metrics-interface]: https://github.com/spotahome/kooper/blob/master/monitoring/metrics/metrics.go -[metrics-example]: https://github.com/spotahome/kooper/tree/master/examples/metrics-controller -[prometheus-registerer]: https://godoc.org/github.com/prometheus/client_golang/prometheus#Registerer -[grafana-dashboard]: https://grafana.com/dashboards/7082 diff --git a/docs/tracing.md b/docs/tracing.md deleted file mode 100644 index f48111e9..00000000 --- a/docs/tracing.md +++ /dev/null @@ -1,134 +0,0 @@ -# Tracing - -Kooper has builting tracing support using [Opentracing][opentracing-url]. This will allow a controller/operator trace all the process from the moment kooper controller starts handling the event until it finishes handling it. The handlers of the objects will receive a `context` where the parent span (coming from Kooper) can be retrieved and continue the trace. - -## Root span - -Kooper will create a trace for each object received using the `Retriever` (using resync/list or watch events). This will create a root span that will set important data. - -### Relevant tags - -* `kubernetes.object.namespace`: The namespace of the object being processed. -* `kubernetes.object.name`: the name of the object being processed. -* `kubernetes.object.key`: Key of the object, with namespace/objectName style. -* `component`: Always kooper. -* `kooper.controller`: The name of the controller (got from the configuration). -* `controller.cfg.*`: Some tags with the configuration of the controller. -* `kubernetes.object.total_processed_times`: the number of times the same object has been processed (1 + retries). -* `kubernetes.object.processing_retry`: Marks if this processing object is a retry of a previous processed one (means that the past time it errored). -* `span.kind`: Always consumer kind. -* `error`: flag marking if it finished with error. - -### Relevant Logs - -The most relevan logs of the root span are the processing and error logs. -this is an example of a log in a processed object: - -* `0.03ms: event=baggage, key=kubernetes.object.key, value=kube-system/kube-dns-7785f4d7dc-4brdg` -* `0.05ms: event=process_object, kubernetes.object.key=kube-system/kube-dns-7785f4d7dc-4brdg` -* `1.32s: event=error, message=randomly failed` -* `1.32s: event=forget, kubernetes.object.key=kube-system/kube-dns-7785f4d7dc-4brdg, message=max number of retries reached after failing, forgetting object key` -* `1.32s: success=false` - -### Baggage - -The [baggage][ot-baggage-url] is data that will be propagated in all the trace. Kooper adds one single baggage (it's expensive, must be careful), is the `kubernetes.object.key` that has the object key of the object being processed that started all the trace. - -## Add/Delete handling span - -After the root span, Kooper will create a new child span from this that will be a processing Add or a Delete depending of what kind of event is. - -## Context on handler - -Handler receives the context that has the parten span. - -```go -type Handler interface { - Add(context.Context, runtime.Object) error - Delete(context.Context, string) error -} -``` - -You can get the parent span using opentracing API. For example: - -```go -hand := &handler.HandlerFunc{ - AddFunc: func(ctx context.Context, obj runtime.Object) error { - // Get the parent span. - pSpan := opentracing.SpanFromContext(ctx) - - // Create a new span. - span := tracer.StartSpan("AddFunc", opentracing.ChildOf(pSpan.Context())) - defer span.Finish() - - // Do stuff... - - return nil - }, - DeleteFunc: func(ctx context.Context, s string) error { - // Get the parent span. - pSpan := opentracing.SpanFromContext(ctx) - - // Create a new span. - span := tracer.StartSpan("DeleteFunc", opentracing.ChildOf(pSpan.Context())) - defer span.Finish() - - // Do stuff... - - return nil - }, -} -``` - -## Span diagram example - -Kooper creates the first two spans and a user using kooper in it's controller creates 4 new spans. - -```text -––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time - - [ Kooper processJob ········································] - [Kooper handleAddObject/handleDeleteObject···············] - [User span1......] - [User span2·····] - [User span3··········] - [User span4····] -``` - -## Tracer Service - -The service name that identifies the traces created by kooper is managed by the tracer instance passed to the controller when creating the controller, this is that the tracer service name will be the trace service name. - -## Example - -Kooper comes with an [example][traced-controller], it's just the pod print controller example with faked calls to different services like Redis or AWS but tracing this calls. It uses the opentracing implementation of [Jaeger][jaeger-url]. - -First you need to run a development instance of Jaeger in localhost, the tracer passed to kooper controller will send the generated traces to this jaeger instance. - -Run jaeger with docker. - -```bash -docker run --rm -it \ - -p5775:5775/udp \ - -p6831:6831/udp \ - -p6832:6832/udp \ - -p5778:5778 \ - -p16686:16686 \ - -p14268:14268 \ - -p9411:9411 \ - jaegertracing/all-in-one:latest -``` - -Now just run the example, you need kubectl set to the cluster, it will make fake calls to faked services and will error randomly on the service calls, making retries on the object processing and creating different kind of traces. - -```bash -go run ./examples/traced-controller/* -``` - -Check the created traces of the controller in [jaeger ui][jaeger-ui-local-url] - -[opentracing-url][http://opentracing.io] -[ot-baggage-url][https://github.com/opentracing/specification/blob/master/specification.md#set-a-baggage-item] -[traced-controller][https://github.com/spotahome/kooper/tree/master/examples/traced-controller] -[jaeger-url][http://www.jaegertracing.io/] -[jaeger-ui-local-url][http://127.0.0.1:16686] \ No newline at end of file diff --git a/examples/config-custom-controller/main.go b/examples/config-custom-controller/main.go index 44d2d2ab..62c73e68 100644 --- a/examples/config-custom-controller/main.go +++ b/examples/config-custom-controller/main.go @@ -2,10 +2,12 @@ package main import ( "context" + "fmt" "os" "path/filepath" "time" + "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -17,15 +19,15 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/operator/retrieve" + "github.com/spotahome/kooper/v2/controller" + "github.com/spotahome/kooper/v2/log" + kooperlogrus "github.com/spotahome/kooper/v2/log/logrus" ) -func main() { +func run() error { // Initialize logger. - log := &log.Std{} + logger := kooperlogrus.New(logrus.NewEntry(logrus.New())). + WithKV(log.KV{"example": "config-custom-controller"}) // Get k8s client. k8scfg, err := rest.InClusterConfig() @@ -34,55 +36,63 @@ func main() { kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config") k8scfg, err = clientcmd.BuildConfigFromFlags("", kubehome) if err != nil { - log.Errorf("error loading kubernetes configuration: %s", err) - os.Exit(1) + return fmt.Errorf("error loading kubernetes configuration: %w", err) } } k8scli, err := kubernetes.NewForConfig(k8scfg) if err != nil { - log.Errorf("error creating kubernetes client: %s", err) - os.Exit(1) + return fmt.Errorf("error creating kubernetes client: %w", err) } // Create our retriever so the controller knows how to get/listen for pod events. - retr := &retrieve.Resource{ - Object: &corev1.Pod{}, - ListerWatcher: &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return k8scli.CoreV1().Pods("").List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return k8scli.CoreV1().Pods("").Watch(options) - }, + retr := controller.MustRetrieverFromListerWatcher(&cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return k8scli.CoreV1().Pods("").List(options) }, - } + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return k8scli.CoreV1().Pods("").Watch(options) + }, + }) // Our domain logic that will print every add/sync/update and delete event we . - hand := &handler.HandlerFunc{ - AddFunc: func(_ context.Context, obj runtime.Object) error { - pod := obj.(*corev1.Pod) - log.Infof("Pod added: %s/%s", pod.Namespace, pod.Name) - return nil - }, - DeleteFunc: func(_ context.Context, s string) error { - log.Infof("Pod deleted: %s", s) - return nil - }, - } + hand := controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error { + pod := obj.(*corev1.Pod) + logger.Infof("Pod added: %s/%s", pod.Namespace, pod.Name) + return nil + }) // Create the controller with custom configuration. cfg := &controller.Config{ + Name: "config-custom-controller", + Handler: hand, + Retriever: retr, + Logger: logger, + ProcessingJobRetries: 5, ResyncInterval: 45 * time.Second, ConcurrentWorkers: 1, } - ctrl := controller.New(cfg, hand, retr, nil, nil, nil, log) + ctrl, err := controller.New(cfg) + if err != nil { + return fmt.Errorf("could not create controller: %w", err) + } // Start our controller. stopC := make(chan struct{}) - if err := ctrl.Run(stopC); err != nil { - log.Errorf("error running controller: %s", err) + err = ctrl.Run(stopC) + if err != nil { + return fmt.Errorf("error running controller: %w", err) + } + + return nil +} + +func main() { + err := run() + if err != nil { + fmt.Fprintf(os.Stderr, "error running app: %s", err) os.Exit(1) } + os.Exit(0) } diff --git a/examples/controller-concurrency-handling-example/main.go b/examples/controller-concurrency-handling-example/main.go deleted file mode 100644 index dfc62c94..00000000 --- a/examples/controller-concurrency-handling-example/main.go +++ /dev/null @@ -1,115 +0,0 @@ -package main - -import ( - "context" - "flag" - "os" - "path/filepath" - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" - - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/operator/retrieve" -) - -var ( - concurrentWorkers int - sleepMS int -) - -func initFlags() error { - fg := flag.NewFlagSet(os.Args[0], flag.ExitOnError) - fg.IntVar(&concurrentWorkers, "concurrency", 0, "The number of concurrent event handling") - fg.IntVar(&sleepMS, "sleep-ms", 25, "The number of milliseconds to sleep on each event handling") - return fg.Parse(os.Args[1:]) -} - -func sleep() { - time.Sleep(time.Duration(sleepMS) * time.Millisecond) -} - -func main() { - // Initialize logger. - log := &log.Std{} - - // Init flags. - if err := initFlags(); err != nil { - log.Errorf("error parsing arguments: %s", err) - os.Exit(1) - } - - // Get k8s client. - k8scfg, err := rest.InClusterConfig() - if err != nil { - // No in cluster? letr's try locally - kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config") - k8scfg, err = clientcmd.BuildConfigFromFlags("", kubehome) - if err != nil { - log.Errorf("error loading kubernetes configuration: %s", err) - os.Exit(1) - } - } - k8scli, err := kubernetes.NewForConfig(k8scfg) - if err != nil { - log.Errorf("error creating kubernetes client: %s", err) - os.Exit(1) - } - - // Create our retriever so the controller knows how to get/listen for pod events. - retr := &retrieve.Resource{ - Object: &corev1.Pod{}, - ListerWatcher: &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return k8scli.CoreV1().Pods("").List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return k8scli.CoreV1().Pods("").Watch(options) - }, - }, - } - - // Our domain logic that will print every add/sync/update and delete event we . - hand := &handler.HandlerFunc{ - AddFunc: func(_ context.Context, obj runtime.Object) error { - pod := obj.(*corev1.Pod) - sleep() - log.Infof("Pod added: %s/%s", pod.Namespace, pod.Name) - return nil - }, - DeleteFunc: func(_ context.Context, s string) error { - sleep() - log.Infof("Pod deleted: %s", s) - return nil - }, - } - - // Create the controller that will refresh every 30 seconds. - var ctrl controller.Controller - if concurrentWorkers < 2 { - log.Infof("sequential controller created") - ctrl = controller.NewSequential(30*time.Second, hand, retr, nil, log) - } else { - log.Infof("sequential controller created") - ctrl, _ = controller.NewConcurrent(concurrentWorkers, 30*time.Second, hand, retr, nil, log) - } - - // Start our controller. - stopC := make(chan struct{}) - if err := ctrl.Run(stopC); err != nil { - log.Errorf("error running controller: %s", err) - os.Exit(1) - } - os.Exit(0) -} diff --git a/examples/controller-concurrency-handling/main.go b/examples/controller-concurrency-handling/main.go new file mode 100644 index 00000000..e7129f64 --- /dev/null +++ b/examples/controller-concurrency-handling/main.go @@ -0,0 +1,133 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" + + "github.com/spotahome/kooper/v2/controller" + "github.com/spotahome/kooper/v2/log" + kooperlogrus "github.com/spotahome/kooper/v2/log/logrus" +) + +var ( + concurrentWorkers int + sleepMS int + intervalS int + retries int + disableResync bool +) + +func initFlags() error { + fg := flag.NewFlagSet(os.Args[0], flag.ExitOnError) + fg.IntVar(&concurrentWorkers, "concurrency", 3, "The number of concurrent event handling") + fg.IntVar(&sleepMS, "sleep-ms", 25, "The number of milliseconds to sleep on each event handling") + fg.IntVar(&intervalS, "interval-s", 45, "The number of seconds to for reconciliation loop intervals") + fg.IntVar(&retries, "retries", 3, "The number of retries in case of error") + fg.BoolVar(&disableResync, "disable-resync", false, "Disables the resync") + + err := fg.Parse(os.Args[1:]) + if err != nil { + return err + } + + return nil +} + +func sleep() { + time.Sleep(time.Duration(sleepMS) * time.Millisecond) +} + +func run() error { + // Initialize logger. + logger := kooperlogrus.New(logrus.NewEntry(logrus.New())). + WithKV(log.KV{"example": "controller-concurrency-handling"}) + + // Init flags. + if err := initFlags(); err != nil { + return fmt.Errorf("error parsing arguments: %w", err) + } + + // Get k8s client. + k8scfg, err := rest.InClusterConfig() + if err != nil { + // No in cluster? letr's try locally + kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config") + k8scfg, err = clientcmd.BuildConfigFromFlags("", kubehome) + if err != nil { + return fmt.Errorf("error loading kubernetes configuration: %w", err) + } + } + k8scli, err := kubernetes.NewForConfig(k8scfg) + if err != nil { + return fmt.Errorf("error creating kubernetes client: %w", err) + } + + // Create our retriever so the controller knows how to get/listen for pod events. + retr := controller.MustRetrieverFromListerWatcher(&cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return k8scli.CoreV1().Pods("").List(options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return k8scli.CoreV1().Pods("").Watch(options) + }, + }) + + // Our domain logic that will print every add/sync/update and delete event we. + hand := controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error { + pod := obj.(*corev1.Pod) + sleep() + logger.Infof("Pod added: %s/%s", pod.Namespace, pod.Name) + return nil + }) + + // Create the controller. + cfg := &controller.Config{ + Name: "controller-concurrency-handling", + Handler: hand, + Retriever: retr, + Logger: logger, + + ProcessingJobRetries: retries, + ResyncInterval: time.Duration(intervalS) * time.Second, + ConcurrentWorkers: concurrentWorkers, + DisableResync: disableResync, + } + ctrl, err := controller.New(cfg) + if err != nil { + return fmt.Errorf("could not create controller: %w", err) + } + + // Start our controller. + stopC := make(chan struct{}) + if err := ctrl.Run(stopC); err != nil { + return fmt.Errorf("error running controller: %w", err) + } + + return nil +} + +func main() { + err := run() + if err != nil { + fmt.Fprintf(os.Stderr, "error running app: %s", err) + os.Exit(1) + } + + os.Exit(0) +} diff --git a/examples/echo-pod-controller/cmd/flags.go b/examples/echo-pod-controller/cmd/flags.go deleted file mode 100644 index a7aea3c9..00000000 --- a/examples/echo-pod-controller/cmd/flags.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "flag" - "os" - "path/filepath" - "time" - - "github.com/spotahome/kooper/examples/echo-pod-controller/controller" - "k8s.io/client-go/util/homedir" -) - -// Flags are the controller flags. -type Flags struct { - flagSet *flag.FlagSet - - Namespace string - ResyncSec int - KubeConfig string - Development bool -} - -// ControllerConfig converts the command line flag arguments to controller configuration. -func (f *Flags) ControllerConfig() controller.Config { - return controller.Config{ - Namespace: f.Namespace, - ResyncPeriod: time.Duration(f.ResyncSec) * time.Second, - } -} - -// NewFlags returns a new Flags. -func NewFlags() *Flags { - f := &Flags{ - flagSet: flag.NewFlagSet(os.Args[0], flag.ExitOnError), - } - // Get the user kubernetes configuration in it's home directory. - kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config") - - // Init flags. - f.flagSet.StringVar(&f.Namespace, "namespace", "", "kubernetes namespace where this app is running") - f.flagSet.IntVar(&f.ResyncSec, "resync-seconds", 30, "The number of seconds the controller will resync the resources") - f.flagSet.StringVar(&f.KubeConfig, "kubeconfig", kubehome, "kubernetes configuration path, only used when development mode enabled") - f.flagSet.BoolVar(&f.Development, "development", false, "development flag will allow to run the operator outside a kubernetes cluster") - - f.flagSet.Parse(os.Args[1:]) - - return f -} diff --git a/examples/echo-pod-controller/cmd/main.go b/examples/echo-pod-controller/cmd/main.go deleted file mode 100644 index b876cf5f..00000000 --- a/examples/echo-pod-controller/cmd/main.go +++ /dev/null @@ -1,99 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/signal" - "syscall" - - applogger "github.com/spotahome/kooper/log" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - - "github.com/spotahome/kooper/examples/echo-pod-controller/controller" - "github.com/spotahome/kooper/examples/echo-pod-controller/log" -) - -// Main is the main program. -type Main struct { - flags *Flags - config controller.Config - logger log.Logger -} - -// New returns the main application. -func New(logger log.Logger) *Main { - f := NewFlags() - return &Main{ - flags: f, - config: f.ControllerConfig(), - logger: logger, - } -} - -// Run runs the app. -func (m *Main) Run(stopC <-chan struct{}) error { - m.logger.Infof("initializing echo controller") - - // Get kubernetes rest client. - k8sCli, err := m.getKubernetesClient() - if err != nil { - return err - } - - // Create the controller and run - ctrl, err := controller.New(m.config, k8sCli, m.logger) - if err != nil { - return err - } - - return ctrl.Run(stopC) -} - -func (m *Main) getKubernetesClient() (kubernetes.Interface, error) { - var err error - var cfg *rest.Config - - // If devel mode then use configuration flag path. - if m.flags.Development { - cfg, err = clientcmd.BuildConfigFromFlags("", m.flags.KubeConfig) - if err != nil { - return nil, fmt.Errorf("could not load configuration: %s", err) - } - } else { - cfg, err = rest.InClusterConfig() - if err != nil { - return nil, fmt.Errorf("error loading kubernetes configuration inside cluster, check app is running outside kubernetes cluster or run in development mode: %s", err) - } - } - - return kubernetes.NewForConfig(cfg) -} - -func main() { - logger := &applogger.Std{} - - stopC := make(chan struct{}) - finishC := make(chan error) - signalC := make(chan os.Signal, 1) - signal.Notify(signalC, syscall.SIGTERM, syscall.SIGINT) - m := New(logger) - - // Run in background the controller. - go func() { - finishC <- m.Run(stopC) - }() - - select { - case err := <-finishC: - if err != nil { - fmt.Fprintf(os.Stderr, "error running controller: %s", err) - os.Exit(1) - } - case <-signalC: - logger.Infof("Signal captured, exiting...") - } - -} diff --git a/examples/echo-pod-controller/controller/config.go b/examples/echo-pod-controller/controller/config.go deleted file mode 100644 index 717dc267..00000000 --- a/examples/echo-pod-controller/controller/config.go +++ /dev/null @@ -1,11 +0,0 @@ -package controller - -import ( - "time" -) - -// Config is the controller configuration. -type Config struct { - ResyncPeriod time.Duration - Namespace string -} diff --git a/examples/echo-pod-controller/controller/echo.go b/examples/echo-pod-controller/controller/echo.go deleted file mode 100644 index d636e2a8..00000000 --- a/examples/echo-pod-controller/controller/echo.go +++ /dev/null @@ -1,54 +0,0 @@ -package controller - -import ( - "context" - - "github.com/spotahome/kooper/operator/controller" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes" - - "github.com/spotahome/kooper/examples/echo-pod-controller/log" - "github.com/spotahome/kooper/examples/echo-pod-controller/service" -) - -// Controller is a controller that echoes pod events. -type Controller struct { - controller.Controller - - config Config - logger log.Logger -} - -// New returns a new Echo controller. -func New(config Config, k8sCli kubernetes.Interface, logger log.Logger) (*Controller, error) { - - ret := NewPodRetrieve(config.Namespace, k8sCli) - echoSrv := service.NewSimpleEcho(logger) - handler := &handler{echoSrv: echoSrv} - - ctrl := controller.NewSequential(config.ResyncPeriod, handler, ret, nil, logger) - - return &Controller{ - Controller: ctrl, - config: config, - logger: logger, - }, nil -} - -const ( - addPrefix = "ADD" - deletePrefix = "DELETE" -) - -type handler struct { - echoSrv service.Echo -} - -func (h *handler) Add(_ context.Context, obj runtime.Object) error { - h.echoSrv.EchoObj(addPrefix, obj) - return nil -} -func (h *handler) Delete(_ context.Context, s string) error { - h.echoSrv.EchoS(deletePrefix, s) - return nil -} diff --git a/examples/echo-pod-controller/controller/retrieve.go b/examples/echo-pod-controller/controller/retrieve.go deleted file mode 100644 index eaf43f0c..00000000 --- a/examples/echo-pod-controller/controller/retrieve.go +++ /dev/null @@ -1,42 +0,0 @@ -package controller - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" -) - -// PodRetrieve knows how to retrieve pods. -type PodRetrieve struct { - namespace string - client kubernetes.Interface -} - -// NewPodRetrieve returns a new pod retriever. -func NewPodRetrieve(namespace string, client kubernetes.Interface) *PodRetrieve { - return &PodRetrieve{ - namespace: namespace, - client: client, - } -} - -// GetListerWatcher knows how to return a listerWatcher of a pod. -func (p *PodRetrieve) GetListerWatcher() cache.ListerWatcher { - - return &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return p.client.CoreV1().Pods(p.namespace).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return p.client.CoreV1().Pods(p.namespace).Watch(options) - }, - } -} - -// GetObject returns the empty pod. -func (p *PodRetrieve) GetObject() runtime.Object { - return &corev1.Pod{} -} diff --git a/examples/echo-pod-controller/log/log.go b/examples/echo-pod-controller/log/log.go deleted file mode 100644 index 36f1fb5d..00000000 --- a/examples/echo-pod-controller/log/log.go +++ /dev/null @@ -1,11 +0,0 @@ -package log - -import ( - "github.com/spotahome/kooper/log" -) - -// Logger is the interface of the controller logger. This is an example -// so our Loggger will be the same as the kooper one. -type Logger interface { - log.Logger -} diff --git a/examples/echo-pod-controller/service/service.go b/examples/echo-pod-controller/service/service.go deleted file mode 100644 index 398c705b..00000000 --- a/examples/echo-pod-controller/service/service.go +++ /dev/null @@ -1,58 +0,0 @@ -package service - -import ( - "fmt" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/spotahome/kooper/examples/echo-pod-controller/log" -) - -// Echo is simple echo service. -type Echo interface { - // EchoObj echoes the received object. - EchoObj(prefix string, obj runtime.Object) - // EchoS echoes the received string. - EchoS(prefix string, s string) -} - -// SimpleEcho echoes the received object name. -type SimpleEcho struct { - logger log.Logger -} - -// NewSimpleEcho returns a new SimpleEcho. -func NewSimpleEcho(logger log.Logger) *SimpleEcho { - return &SimpleEcho{ - logger: logger, - } -} - -func (s *SimpleEcho) getObjInfo(obj runtime.Object) (string, error) { - objMeta, ok := obj.(metav1.Object) - if !ok { - return "", fmt.Errorf("could not print object information") - } - return fmt.Sprintf("%s", objMeta.GetName()), nil -} - -func (s *SimpleEcho) echo(prefix string, str string) { - s.logger.Infof("[%s] %s", prefix, str) -} - -// EchoObj satisfies service.Echo interface. -func (s *SimpleEcho) EchoObj(prefix string, obj runtime.Object) { - // Get object string with all the information. - objInfo, err := s.getObjInfo(obj) - if err != nil { - s.logger.Errorf("error on echo: %s", err) - } - - s.echo(prefix, objInfo) -} - -// EchoS satisfies service.Echo interface. -func (s *SimpleEcho) EchoS(prefix string, str string) { - s.echo(prefix, str) -} diff --git a/examples/echo-pod-controller/service/service_test.go b/examples/echo-pod-controller/service/service_test.go deleted file mode 100644 index bfc591ab..00000000 --- a/examples/echo-pod-controller/service/service_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package service_test - -import ( - "fmt" - "sync" - "testing" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/spotahome/kooper/examples/echo-pod-controller/service" -) - -type logKind int - -const ( - infoKind logKind = iota - warnignKind - errorKind -) - -type logEvent struct { - kind logKind - line string -} - -type testLogger struct { - events []logEvent - sync.Mutex -} - -func (t *testLogger) logLine(kind logKind, format string, args ...interface{}) { - str := fmt.Sprintf(format, args...) - t.events = append(t.events, logEvent{kind: kind, line: str}) -} - -func (t *testLogger) Infof(format string, args ...interface{}) { - t.logLine(infoKind, format, args...) -} -func (t *testLogger) Warningf(format string, args ...interface{}) { - t.logLine(warnignKind, format, args...) -} -func (t *testLogger) Errorf(format string, args ...interface{}) { - t.logLine(errorKind, format, args...) -} - -func TestEchoServiceEchoString(t *testing.T) { - tests := []struct { - name string - prefix string - msg string - expResults []logEvent - }{ - { - name: "Logging a prefix and a string should log.", - prefix: "test", - msg: "this is a test", - expResults: []logEvent{ - logEvent{kind: infoKind, line: "[test] this is a test"}, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - - // Mocks. - ml := &testLogger{events: []logEvent{}} - - // Create aservice and run. - srv := service.NewSimpleEcho(ml) - srv.EchoS(test.prefix, test.msg) - - // Check. - assert.Equal(test.expResults, ml.events) - }) - } -} - -func TestEchoServiceEchoObj(t *testing.T) { - tests := []struct { - name string - prefix string - obj runtime.Object - expResults []logEvent - }{ - { - name: "Logging a pod should print pod name.", - prefix: "test", - obj: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mypod", - }, - }, - expResults: []logEvent{ - logEvent{kind: infoKind, line: "[test] mypod"}, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - - // Mocks. - ml := &testLogger{events: []logEvent{}} - - // Create aservice and run. - srv := service.NewSimpleEcho(ml) - srv.EchoObj(test.prefix, test.obj) - - // Check. - assert.Equal(test.expResults, ml.events) - }) - } -} diff --git a/examples/leader-election-controller/main.go b/examples/leader-election-controller/main.go index 5c4dbc3a..799720a1 100644 --- a/examples/leader-election-controller/main.go +++ b/examples/leader-election-controller/main.go @@ -10,6 +10,7 @@ import ( "syscall" "time" + "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -21,11 +22,10 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/controller/leaderelection" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/operator/retrieve" + "github.com/spotahome/kooper/v2/controller" + "github.com/spotahome/kooper/v2/controller/leaderelection" + "github.com/spotahome/kooper/v2/log" + kooperlogrus "github.com/spotahome/kooper/v2/log/logrus" ) const ( @@ -53,18 +53,18 @@ func NewFlags() *Flags { return flags } -// Main runs the main application. -func Main() error { +func run() error { // Flags fl := NewFlags() // Initialize logger. - logger := &log.Std{} + logger := kooperlogrus.New(logrus.NewEntry(logrus.New())). + WithKV(log.KV{"example": "leader-election-controller"}) // Get k8s client. k8scfg, err := rest.InClusterConfig() if err != nil { - // No in cluster? letr's try locally + // No in cluster? lets try locally kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config") k8scfg, err = clientcmd.BuildConfigFromFlags("", kubehome) if err != nil { @@ -77,30 +77,21 @@ func Main() error { } // Create our retriever so the controller knows how to get/listen for pod events. - retr := &retrieve.Resource{ - Object: &corev1.Pod{}, - ListerWatcher: &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return k8scli.CoreV1().Pods("").List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return k8scli.CoreV1().Pods("").Watch(options) - }, + retr := controller.MustRetrieverFromListerWatcher(&cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return k8scli.CoreV1().Pods("").List(options) }, - } + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return k8scli.CoreV1().Pods("").Watch(options) + }, + }) // Our domain logic that will print every add/sync/update and delete event we . - hand := &handler.HandlerFunc{ - AddFunc: func(_ context.Context, obj runtime.Object) error { - pod := obj.(*corev1.Pod) - logger.Infof("Pod added: %s/%s", pod.Namespace, pod.Name) - return nil - }, - DeleteFunc: func(_ context.Context, s string) error { - logger.Infof("Pod deleted: %s", s) - return nil - }, - } + hand := controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error { + pod := obj.(*corev1.Pod) + logger.Infof("Pod added: %s/%s", pod.Namespace, pod.Name) + return nil + }) // Leader election service. lesvc, err := leaderelection.NewDefault(leaderElectionKey, fl.Namespace, k8scli, logger) @@ -110,11 +101,20 @@ func Main() error { // Create the controller and run. cfg := &controller.Config{ + Name: "leader-election-controller", + Handler: hand, + Retriever: retr, + LeaderElector: lesvc, + Logger: logger, + ProcessingJobRetries: 5, ResyncInterval: time.Duration(fl.ResyncIntervalSeconds) * time.Second, ConcurrentWorkers: 1, } - ctrl := controller.New(cfg, hand, retr, lesvc, nil, nil, logger) + ctrl, err := controller.New(cfg) + if err != nil { + return fmt.Errorf("error creating controller: %w", err) + } stopC := make(chan struct{}) errC := make(chan error) go func() { @@ -142,7 +142,7 @@ func Main() error { } func main() { - if err := Main(); err != nil { + if err := run(); err != nil { fmt.Fprintf(os.Stderr, "error executing controller: %s", err) os.Exit(1) } diff --git a/examples/metrics-controller/main.go b/examples/metrics-controller/main.go index ac5b5629..650a89aa 100644 --- a/examples/metrics-controller/main.go +++ b/examples/metrics-controller/main.go @@ -12,7 +12,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - corev1 "k8s.io/api/core/v1" + "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" @@ -23,11 +23,10 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/monitoring/metrics" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/operator/retrieve" + "github.com/spotahome/kooper/v2/controller" + "github.com/spotahome/kooper/v2/log" + kooperlogrus "github.com/spotahome/kooper/v2/log/logrus" + kooperprometheus "github.com/spotahome/kooper/v2/metrics/prometheus" ) const ( @@ -63,11 +62,11 @@ func errRandomly() error { } // creates prometheus recorder and starts serving metrics in background. -func createPrometheusRecorder(logger log.Logger) metrics.Recorder { +func createPrometheusRecorder(logger log.Logger) *kooperprometheus.Recorder { // We could use also prometheus global registry (the default one) // prometheus.DefaultRegisterer instead of creating a new one reg := prometheus.NewRegistry() - m := metrics.NewPrometheus(reg) + rec := kooperprometheus.New(kooperprometheus.Config{Registerer: reg}) // Start serving metrics in background. h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) @@ -76,27 +75,17 @@ func createPrometheusRecorder(logger log.Logger) metrics.Recorder { http.ListenAndServe(metricsAddr, h) }() - return m + return rec } -func getMetricRecorder(backend string, logger log.Logger) (metrics.Recorder, error) { - switch backend { - case prometheusBackend: - logger.Infof("using Prometheus metrics recorder") - return createPrometheusRecorder(logger), nil - } - - return nil, fmt.Errorf("wrong metrics backend") -} - -func main() { +func run() error { // Initialize logger. - log := &log.Std{} + logger := kooperlogrus.New(logrus.NewEntry(logrus.New())). + WithKV(log.KV{"example": "metrics-controller"}) // Init flags. if err := initFlags(); err != nil { - log.Errorf("error parsing arguments: %s", err) - os.Exit(1) + return fmt.Errorf("error parsing arguments: %w", err) } // Get k8s client. @@ -106,57 +95,59 @@ func main() { kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config") k8scfg, err = clientcmd.BuildConfigFromFlags("", kubehome) if err != nil { - log.Errorf("error loading kubernetes configuration: %s", err) - os.Exit(1) + return fmt.Errorf("error loading kubernetes configuration: %w", err) } } k8scli, err := kubernetes.NewForConfig(k8scfg) if err != nil { - log.Errorf("error creating kubernetes client: %s", err) - os.Exit(1) + return fmt.Errorf("error creating kubernetes client: %w", err) } // Create our retriever so the controller knows how to get/listen for pod events. - retr := &retrieve.Resource{ - Object: &corev1.Pod{}, - ListerWatcher: &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return k8scli.CoreV1().Pods("").List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return k8scli.CoreV1().Pods("").Watch(options) - }, + retr := controller.MustRetrieverFromListerWatcher(&cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return k8scli.CoreV1().Pods("").List(options) }, - } + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return k8scli.CoreV1().Pods("").Watch(options) + }, + }) // Our domain logic that will print every add/sync/update and delete event we . - hand := &handler.HandlerFunc{ - AddFunc: func(_ context.Context, obj runtime.Object) error { - sleepRandomly() - return errRandomly() - }, - DeleteFunc: func(_ context.Context, s string) error { - sleepRandomly() - return errRandomly() - }, - } + hand := controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error { + sleepRandomly() + return errRandomly() + }) // Create the controller that will refresh every 30 seconds. - m, err := getMetricRecorder(metricsBackend, log) - if err != nil { - log.Errorf("errors getting metrics backend: %s", err) - os.Exit(1) - } cfg := &controller.Config{ - Name: "metricsControllerTest", + Name: "metricsControllerTest", + Handler: hand, + Retriever: retr, + MetricsRecorder: createPrometheusRecorder(logger), + Logger: logger, + ProcessingJobRetries: 3, + } + ctrl, err := controller.New(cfg) + if err != nil { + return fmt.Errorf("could not create controller: %w", err) } - ctrl := controller.New(cfg, hand, retr, nil, nil, m, log) // Start our controller. stopC := make(chan struct{}) if err := ctrl.Run(stopC); err != nil { - log.Errorf("error running controller: %s", err) + return fmt.Errorf("error running controller: %w", err) + } + + return nil +} + +func main() { + err := run() + if err != nil { + fmt.Fprintf(os.Stderr, "error running app: %s", err) os.Exit(1) } + os.Exit(0) } diff --git a/examples/multi-resource-controller/main.go b/examples/multi-resource-controller/main.go new file mode 100644 index 00000000..7880bd68 --- /dev/null +++ b/examples/multi-resource-controller/main.go @@ -0,0 +1,142 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" + + "github.com/spotahome/kooper/v2/controller" + "github.com/spotahome/kooper/v2/log" + kooperlogrus "github.com/spotahome/kooper/v2/log/logrus" +) + +func run() error { + // Initialize logger. + logger := kooperlogrus.New(logrus.NewEntry(logrus.New())). + WithKV(log.KV{"example": "multi-resource-controller"}) + + // Get k8s client. + k8scfg, err := rest.InClusterConfig() + if err != nil { + // No in cluster? letr's try locally + kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config") + k8scfg, err = clientcmd.BuildConfigFromFlags("", kubehome) + if err != nil { + return fmt.Errorf("error loading kubernetes configuration: %w", err) + } + } + k8scli, err := kubernetes.NewForConfig(k8scfg) + if err != nil { + return fmt.Errorf("error creating kubernetes client: %w", err) + } + + // Our domain logic that will print every add/sync/update and delete event we . + hand := controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error { + dep, ok := obj.(*appsv1.Deployment) + if ok { + logger.Infof("Deployment added: %s/%s", dep.Namespace, dep.Name) + return nil + } + + st, ok := obj.(*appsv1.StatefulSet) + if ok { + logger.Infof("Statefulset added: %s/%s", st.Namespace, st.Name) + return nil + } + + return nil + }) + + const ( + retries = 5 + resyncInterval = 45 * time.Second + workers = 1 + ) + + // Create the controller for deployments. + ctrlDep, err := controller.New(&controller.Config{ + Name: "multi-resource-controller-deployments", + Handler: hand, + Retriever: controller.MustRetrieverFromListerWatcher( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return k8scli.AppsV1().Deployments("").List(options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return k8scli.AppsV1().Deployments("").Watch(options) + }, + }, + ), + Logger: logger, + ProcessingJobRetries: retries, + ResyncInterval: resyncInterval, + ConcurrentWorkers: workers, + }) + if err != nil { + return fmt.Errorf("could not create deployment resource controller: %w", err) + } + + // Create the controller for statefulsets. + ctrlSt, err := controller.New(&controller.Config{ + Name: "multi-resource-controller-statefulsets", + Handler: hand, + Retriever: controller.MustRetrieverFromListerWatcher( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return k8scli.AppsV1().StatefulSets("").List(options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return k8scli.AppsV1().StatefulSets("").Watch(options) + }, + }, + ), + Logger: logger, + ProcessingJobRetries: retries, + ResyncInterval: resyncInterval, + ConcurrentWorkers: workers, + }) + + // Start our controllers. + stopC := make(chan struct{}) + defer close(stopC) + errC := make(chan error) + go func() { + errC <- ctrlDep.Run(stopC) + }() + + go func() { + errC <- ctrlSt.Run(stopC) + }() + + // Wait until one finishes. + err = <-errC + if err != nil { + return fmt.Errorf("error running controllers: %w", err) + } + + return nil +} + +func main() { + err := run() + if err != nil { + fmt.Fprintf(os.Stderr, "error running app: %s", err) + os.Exit(1) + } + + os.Exit(0) +} diff --git a/examples/onefile-echo-pod-controller/main.go b/examples/onefile-echo-pod-controller/main.go deleted file mode 100644 index edbeb3bb..00000000 --- a/examples/onefile-echo-pod-controller/main.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "context" - "os" - "path/filepath" - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" - - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/operator/retrieve" -) - -func main() { - // Initialize logger. - log := &log.Std{} - - // Get k8s client. - k8scfg, err := rest.InClusterConfig() - if err != nil { - // No in cluster? letr's try locally - kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config") - k8scfg, err = clientcmd.BuildConfigFromFlags("", kubehome) - if err != nil { - log.Errorf("error loading kubernetes configuration: %s", err) - os.Exit(1) - } - } - k8scli, err := kubernetes.NewForConfig(k8scfg) - if err != nil { - log.Errorf("error creating kubernetes client: %s", err) - os.Exit(1) - } - - // Create our retriever so the controller knows how to get/listen for pod events. - retr := &retrieve.Resource{ - Object: &corev1.Pod{}, - ListerWatcher: &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return k8scli.CoreV1().Pods("").List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return k8scli.CoreV1().Pods("").Watch(options) - }, - }, - } - - // Our domain logic that will print every add/sync/update and delete event we . - hand := &handler.HandlerFunc{ - AddFunc: func(_ context.Context, obj runtime.Object) error { - pod := obj.(*corev1.Pod) - log.Infof("Pod added: %s/%s", pod.Namespace, pod.Name) - return nil - }, - DeleteFunc: func(_ context.Context, s string) error { - log.Infof("Pod deleted: %s", s) - return nil - }, - } - - // Create the controller that will refresh every 30 seconds. - ctrl := controller.NewSequential(30*time.Second, hand, retr, nil, log) - - // Start our controller. - stopC := make(chan struct{}) - if err := ctrl.Run(stopC); err != nil { - log.Errorf("error running controller: %s", err) - os.Exit(1) - } - os.Exit(0) -} diff --git a/examples/pod-terminator-operator/Makefile b/examples/pod-terminator-operator/Makefile index 1a63bddb..8cf0f91d 100644 --- a/examples/pod-terminator-operator/Makefile +++ b/examples/pod-terminator-operator/Makefile @@ -1,14 +1,26 @@ -CODE_GENERATOR_IMAGE := quay.io/slok/kube-code-generator:v1.13.5 +CODE_GENERATOR_IMAGE := quay.io/slok/kube-code-generator:v1.17.3 +CRD_GENERATOR_IMAGE := quay.io/slok/kube-code-generator:latest DIRECTORY := $(PWD) +ROOT_DIRECTORY := $(DIRECTORY)/../.. CODE_GENERATOR_PACKAGE := github.com/spotahome/kooper/examples/pod-terminator-operator -generate: +generate: generate-client generate-crd + +generate-client: docker run --rm -it \ - -v $(DIRECTORY):/go/src/$(CODE_GENERATOR_PACKAGE) \ - -e PROJECT_PACKAGE=$(CODE_GENERATOR_PACKAGE) \ - -e CLIENT_GENERATOR_OUT=$(CODE_GENERATOR_PACKAGE)/client/k8s \ - -e APIS_ROOT=$(CODE_GENERATOR_PACKAGE)/apis \ - -e GROUPS_VERSION="chaos:v1alpha1" \ - -e GENERATION_TARGETS="deepcopy,client" \ - $(CODE_GENERATOR_IMAGE) \ No newline at end of file + -v $(DIRECTORY):/go/src/$(CODE_GENERATOR_PACKAGE) \ + -e PROJECT_PACKAGE=$(CODE_GENERATOR_PACKAGE) \ + -e CLIENT_GENERATOR_OUT=$(CODE_GENERATOR_PACKAGE)/client/k8s \ + -e APIS_ROOT=$(CODE_GENERATOR_PACKAGE)/apis \ + -e GROUPS_VERSION="chaos:v1alpha1" \ + -e GENERATION_TARGETS="deepcopy,client" \ + $(CODE_GENERATOR_IMAGE) + +generate-crd: + docker run -it --rm \ + -v $(ROOT_DIRECTORY):/src \ + -e GO_PROJECT_ROOT=/src/examples/pod-terminator-operator \ + -e CRD_TYPES_PATH=/src/examples/pod-terminator-operator/apis \ + -e CRD_OUT_PATH=/src/examples/pod-terminator-operator/manifests \ + $(CRD_GENERATOR_IMAGE) update-crd.sh \ No newline at end of file diff --git a/examples/pod-terminator-operator/apis/chaos/v1alpha1/register.go b/examples/pod-terminator-operator/apis/chaos/v1alpha1/register.go index baa763ac..88cdb68b 100644 --- a/examples/pod-terminator-operator/apis/chaos/v1alpha1/register.go +++ b/examples/pod-terminator-operator/apis/chaos/v1alpha1/register.go @@ -6,7 +6,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - podterminatoroperatorchaos "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos" + podterminatoroperatorchaos "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos" ) const ( diff --git a/examples/pod-terminator-operator/apis/chaos/v1alpha1/types.go b/examples/pod-terminator-operator/apis/chaos/v1alpha1/types.go index 54fba233..5c3fed5f 100644 --- a/examples/pod-terminator-operator/apis/chaos/v1alpha1/types.go +++ b/examples/pod-terminator-operator/apis/chaos/v1alpha1/types.go @@ -4,11 +4,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// PodTerminator represents a pod terminator. +// // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodTerminator represents a pod terminator. +// +kubebuilder:resource:singular=podterminator,path=podterminators,shortName=pt,scope=Cluster,categories=terminators;killers;gc type PodTerminator struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. diff --git a/examples/pod-terminator-operator/apis/chaos/v1alpha1/zz_generated.deepcopy.go b/examples/pod-terminator-operator/apis/chaos/v1alpha1/zz_generated.deepcopy.go index 3062860b..70c55551 100644 --- a/examples/pod-terminator-operator/apis/chaos/v1alpha1/zz_generated.deepcopy.go +++ b/examples/pod-terminator-operator/apis/chaos/v1alpha1/zz_generated.deepcopy.go @@ -55,7 +55,7 @@ func (in *PodTerminator) DeepCopyObject() runtime.Object { func (in *PodTerminatorList) DeepCopyInto(out *PodTerminatorList) { *out = *in out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]PodTerminator, len(*in)) diff --git a/examples/pod-terminator-operator/client/k8s/clientset/versioned/clientset.go b/examples/pod-terminator-operator/client/k8s/clientset/versioned/clientset.go index c611d67f..fc79d7ea 100644 --- a/examples/pod-terminator-operator/client/k8s/clientset/versioned/clientset.go +++ b/examples/pod-terminator-operator/client/k8s/clientset/versioned/clientset.go @@ -19,7 +19,9 @@ limitations under the License. package versioned import ( - chaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1" + "fmt" + + chaosv1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1" discovery "k8s.io/client-go/discovery" rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" @@ -28,8 +30,6 @@ import ( type Interface interface { Discovery() discovery.DiscoveryInterface ChaosV1alpha1() chaosv1alpha1.ChaosV1alpha1Interface - // Deprecated: please explicitly pick a version if possible. - Chaos() chaosv1alpha1.ChaosV1alpha1Interface } // Clientset contains the clients for groups. Each group has exactly one @@ -44,12 +44,6 @@ func (c *Clientset) ChaosV1alpha1() chaosv1alpha1.ChaosV1alpha1Interface { return c.chaosV1alpha1 } -// Deprecated: Chaos retrieves the default version of ChaosClient. -// Please explicitly pick a version. -func (c *Clientset) Chaos() chaosv1alpha1.ChaosV1alpha1Interface { - return c.chaosV1alpha1 -} - // Discovery retrieves the DiscoveryClient func (c *Clientset) Discovery() discovery.DiscoveryInterface { if c == nil { @@ -59,9 +53,14 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface { } // NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. func NewForConfig(c *rest.Config) (*Clientset, error) { configShallowCopy := *c if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("Burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) } var cs Clientset diff --git a/examples/pod-terminator-operator/client/k8s/clientset/versioned/fake/clientset_generated.go b/examples/pod-terminator-operator/client/k8s/clientset/versioned/fake/clientset_generated.go index f162233f..07e84df9 100644 --- a/examples/pod-terminator-operator/client/k8s/clientset/versioned/fake/clientset_generated.go +++ b/examples/pod-terminator-operator/client/k8s/clientset/versioned/fake/clientset_generated.go @@ -19,9 +19,9 @@ limitations under the License. package fake import ( - clientset "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned" - chaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1" - fakechaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake" + clientset "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/client/k8s/clientset/versioned" + chaosv1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1" + fakechaosv1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" @@ -41,7 +41,7 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset { } } - cs := &Clientset{} + cs := &Clientset{tracker: o} cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} cs.AddReactor("*", "*", testing.ObjectReaction(o)) cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { @@ -63,20 +63,20 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset { type Clientset struct { testing.Fake discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker } func (c *Clientset) Discovery() discovery.DiscoveryInterface { return c.discovery } +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + var _ clientset.Interface = &Clientset{} // ChaosV1alpha1 retrieves the ChaosV1alpha1Client func (c *Clientset) ChaosV1alpha1() chaosv1alpha1.ChaosV1alpha1Interface { return &fakechaosv1alpha1.FakeChaosV1alpha1{Fake: &c.Fake} } - -// Chaos retrieves the ChaosV1alpha1Client -func (c *Clientset) Chaos() chaosv1alpha1.ChaosV1alpha1Interface { - return &fakechaosv1alpha1.FakeChaosV1alpha1{Fake: &c.Fake} -} diff --git a/examples/pod-terminator-operator/client/k8s/clientset/versioned/fake/register.go b/examples/pod-terminator-operator/client/k8s/clientset/versioned/fake/register.go index 53afb58c..1f734d51 100644 --- a/examples/pod-terminator-operator/client/k8s/clientset/versioned/fake/register.go +++ b/examples/pod-terminator-operator/client/k8s/clientset/versioned/fake/register.go @@ -19,7 +19,7 @@ limitations under the License. package fake import ( - chaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" + chaosv1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/examples/pod-terminator-operator/client/k8s/clientset/versioned/scheme/register.go b/examples/pod-terminator-operator/client/k8s/clientset/versioned/scheme/register.go index 517200c1..9b2c3aab 100644 --- a/examples/pod-terminator-operator/client/k8s/clientset/versioned/scheme/register.go +++ b/examples/pod-terminator-operator/client/k8s/clientset/versioned/scheme/register.go @@ -19,7 +19,7 @@ limitations under the License. package scheme import ( - chaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" + chaosv1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/chaos_client.go b/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/chaos_client.go index 0257153a..d593bca6 100644 --- a/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/chaos_client.go +++ b/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/chaos_client.go @@ -19,9 +19,8 @@ limitations under the License. package v1alpha1 import ( - v1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" - "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned/scheme" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" + v1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos/v1alpha1" + "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/client/k8s/clientset/versioned/scheme" rest "k8s.io/client-go/rest" ) @@ -71,7 +70,7 @@ func setConfigDefaults(config *rest.Config) error { gv := v1alpha1.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" - config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() diff --git a/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake/fake_chaos_client.go b/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake/fake_chaos_client.go index 0472433c..051cfa03 100644 --- a/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake/fake_chaos_client.go +++ b/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake/fake_chaos_client.go @@ -19,7 +19,7 @@ limitations under the License. package fake import ( - v1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1" + v1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1" rest "k8s.io/client-go/rest" testing "k8s.io/client-go/testing" ) diff --git a/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake/fake_podterminator.go b/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake/fake_podterminator.go index 79158494..b6f29f86 100644 --- a/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake/fake_podterminator.go +++ b/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/fake/fake_podterminator.go @@ -19,7 +19,7 @@ limitations under the License. package fake import ( - v1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" + v1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" schema "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/podterminator.go b/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/podterminator.go index 5bfa00f7..7c866b3f 100644 --- a/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/podterminator.go +++ b/examples/pod-terminator-operator/client/k8s/clientset/versioned/typed/chaos/v1alpha1/podterminator.go @@ -21,8 +21,8 @@ package v1alpha1 import ( "time" - v1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" - scheme "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned/scheme" + v1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos/v1alpha1" + scheme "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/client/k8s/clientset/versioned/scheme" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" diff --git a/examples/pod-terminator-operator/cmd/flags.go b/examples/pod-terminator-operator/cmd/flags.go index 523e1513..5ce61f9b 100644 --- a/examples/pod-terminator-operator/cmd/flags.go +++ b/examples/pod-terminator-operator/cmd/flags.go @@ -8,7 +8,7 @@ import ( "k8s.io/client-go/util/homedir" - "github.com/spotahome/kooper/examples/pod-terminator-operator/operator" + "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/operator" ) // Flags are the controller flags. diff --git a/examples/pod-terminator-operator/cmd/main.go b/examples/pod-terminator-operator/cmd/main.go index 019e2114..c7cc5dad 100644 --- a/examples/pod-terminator-operator/cmd/main.go +++ b/examples/pod-terminator-operator/cmd/main.go @@ -7,17 +7,17 @@ import ( "syscall" "time" - "github.com/spotahome/kooper/client/crd" - applogger "github.com/spotahome/kooper/log" - apiextensionscli "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - podtermk8scli "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned" - "github.com/spotahome/kooper/examples/pod-terminator-operator/log" - "github.com/spotahome/kooper/examples/pod-terminator-operator/operator" + podtermk8scli "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/client/k8s/clientset/versioned" + "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/log" + "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/operator" + kooperlog "github.com/spotahome/kooper/v2/log" + kooperlogrus "github.com/spotahome/kooper/v2/log/logrus" ) // Main is the main program. @@ -42,13 +42,13 @@ func (m *Main) Run(stopC <-chan struct{}) error { m.logger.Infof("initializing pod termination operator") // Get kubernetes rest client. - ptCli, crdCli, k8sCli, err := m.getKubernetesClients() + ptCli, k8sCli, err := m.getKubernetesClients() if err != nil { return err } // Create the operator and run - op, err := operator.New(m.config, ptCli, crdCli, k8sCli, m.logger) + op, err := operator.New(m.config, ptCli, k8sCli, m.logger) if err != nil { return err } @@ -58,7 +58,7 @@ func (m *Main) Run(stopC <-chan struct{}) error { // getKubernetesClients returns all the required clients to communicate with // kubernetes cluster: CRD type client, pod terminator types client, kubernetes core types client. -func (m *Main) getKubernetesClients() (podtermk8scli.Interface, crd.Interface, kubernetes.Interface, error) { +func (m *Main) getKubernetesClients() (podtermk8scli.Interface, kubernetes.Interface, error) { var err error var cfg *rest.Config @@ -66,39 +66,33 @@ func (m *Main) getKubernetesClients() (podtermk8scli.Interface, crd.Interface, k if m.flags.Development { cfg, err = clientcmd.BuildConfigFromFlags("", m.flags.KubeConfig) if err != nil { - return nil, nil, nil, fmt.Errorf("could not load configuration: %s", err) + return nil, nil, fmt.Errorf("could not load configuration: %s", err) } } else { cfg, err = rest.InClusterConfig() if err != nil { - return nil, nil, nil, fmt.Errorf("error loading kubernetes configuration inside cluster, check app is running outside kubernetes cluster or run in development mode: %s", err) + return nil, nil, fmt.Errorf("error loading kubernetes configuration inside cluster, check app is running outside kubernetes cluster or run in development mode: %s", err) } } // Create clients. k8sCli, err := kubernetes.NewForConfig(cfg) if err != nil { - return nil, nil, nil, err + return nil, nil, err } // App CRD k8s types client. ptCli, err := podtermk8scli.NewForConfig(cfg) if err != nil { - return nil, nil, nil, err + return nil, nil, err } - // CRD cli. - aexCli, err := apiextensionscli.NewForConfig(cfg) - if err != nil { - return nil, nil, nil, err - } - crdCli := crd.NewClient(aexCli, m.logger) - - return ptCli, crdCli, k8sCli, nil + return ptCli, k8sCli, nil } func main() { - logger := &applogger.Std{} + logger := kooperlogrus.New(logrus.NewEntry(logrus.New())). + WithKV(kooperlog.KV{"example": "pod-terminator-operator"}) stopC := make(chan struct{}) finishC := make(chan error) diff --git a/examples/pod-terminator-operator/go.mod b/examples/pod-terminator-operator/go.mod new file mode 100644 index 00000000..f81e1c57 --- /dev/null +++ b/examples/pod-terminator-operator/go.mod @@ -0,0 +1,15 @@ +module github.com/spotahome/kooper/examples/pod-terminator-operator + +go 1.14 + +replace github.com/spotahome/kooper => ../../ + +require ( + github.com/sirupsen/logrus v1.5.0 + github.com/spotahome/kooper v0.0.0 + github.com/stretchr/testify v1.5.1 + k8s.io/api v0.17.4 + k8s.io/apiextensions-apiserver v0.15.10 + k8s.io/apimachinery v0.17.4 + k8s.io/client-go v0.17.4 +) diff --git a/examples/pod-terminator-operator/go.sum b/examples/pod-terminator-operator/go.sum new file mode 100644 index 00000000..d2e303a8 --- /dev/null +++ b/examples/pod-terminator-operator/go.sum @@ -0,0 +1,395 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtzYCrT+um/rrSQ1BfA= +github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +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/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-oidc v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda h1:NyywMz59neOoVRFDz+ccfKWxn784fiHMDnZSy6T+JXY= +github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550 h1:mV9jbLoSW/8m4VK16ZkHTozJa8sesK5u5kTMFysTYac= +github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +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.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8 h1:L9JPKrtsHMQ4VCRQfHvbbHBfB2Urn8xf6QZeXZ+OrN4= +github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.0.0-20141017032234-72f9bd7c4e0c/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= +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.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3 h1:EooPXg51Tn+xmWPXJUGCnJhJSpeuMlBmfJVcqIRmmv8= +github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +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/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68= +golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g= +golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.1/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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.15.10 h1:g6t2OLNjupSeoepE0zlIcvoT6Q+QDfdfEUwk5lwHXAo= +k8s.io/api v0.15.10/go.mod h1:PffiEKNf0aFiv2naEYSGTFHIGA9V8Qwt22DZIAokOzQ= +k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA= +k8s.io/apiextensions-apiserver v0.15.10 h1:S5CJaYVIceZ58yg/AkyPPS+X2CPLkB6DpY9+pFHoHd0= +k8s.io/apiextensions-apiserver v0.15.10/go.mod h1:W0KZ5vYUt8GuAgEVfRcx4NUBXs/Gp5X76hrjvi7nTZE= +k8s.io/apimachinery v0.15.10/go.mod h1:ZRw+v83FjgEqlzqaBkxL3XB21MSLYdzjsY9Bgxclhdw= +k8s.io/apimachinery v0.15.12-beta.0 h1:aGvobE1kXnMyyAgzsYe6bfyyAcoIy2vqwPtSO/PgGBg= +k8s.io/apimachinery v0.15.12-beta.0/go.mod h1:ZRw+v83FjgEqlzqaBkxL3XB21MSLYdzjsY9Bgxclhdw= +k8s.io/apimachinery v0.17.4 h1:UzM+38cPUJnzqSQ+E1PY4YxMHIzQyCg29LOoGfo79Zw= +k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/apiserver v0.15.10/go.mod h1:6KNMxtnZD0q3kmaRiBF8xyuBeF/D6HlAHgtOR0CsNBc= +k8s.io/client-go v0.15.10 h1:WyH6P2wgULDTFqqClm8cP1E2ELj2qbrxJGR76/SLBQw= +k8s.io/client-go v0.15.10/go.mod h1:UkLY6FcnDCJhI7sabgHI+WovVUTZKvmcOxFrSLOVr4g= +k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc= +k8s.io/code-generator v0.15.10/go.mod h1:G8bQwmHm2eafm5bgtX67XDZQ8CWKSGu9DekI+yN4Y5I= +k8s.io/component-base v0.15.10/go.mod h1:cYv0GMIaJQEHd1Pgdva5eBRxu1ZXwi1dyYAM0vRhyW0= +k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68= +k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4= +k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/examples/pod-terminator-operator/log/log.go b/examples/pod-terminator-operator/log/log.go index a52f92e2..8d7e1e8f 100644 --- a/examples/pod-terminator-operator/log/log.go +++ b/examples/pod-terminator-operator/log/log.go @@ -1,7 +1,7 @@ package log import ( - "github.com/spotahome/kooper/log" + "github.com/spotahome/kooper/v2/log" ) // Logger is the interface of the operator logger. This is an example diff --git a/examples/pod-terminator-operator/manifest-examples/pause.yaml b/examples/pod-terminator-operator/manifest-examples/pause.yaml index 63e5d8f5..3ca0001a 100644 --- a/examples/pod-terminator-operator/manifest-examples/pause.yaml +++ b/examples/pod-terminator-operator/manifest-examples/pause.yaml @@ -1,7 +1,8 @@ -apiVersion: apps/v1beta2 +apiVersion: apps/v1 kind: Deployment metadata: name: pause-pods + namespace: kooper-test labels: application: pause spec: diff --git a/examples/pod-terminator-operator/manifests/chaos.spotahome.com_podterminators.yaml b/examples/pod-terminator-operator/manifests/chaos.spotahome.com_podterminators.yaml new file mode 100644 index 00000000..69c26a3a --- /dev/null +++ b/examples/pod-terminator-operator/manifests/chaos.spotahome.com_podterminators.yaml @@ -0,0 +1,80 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: (devel) + creationTimestamp: null + name: podterminators.chaos.spotahome.com +spec: + group: chaos.spotahome.com + names: + categories: + - terminators + - killers + - gc + kind: PodTerminator + listKind: PodTerminatorList + plural: podterminators + shortNames: + - pt + singular: podterminator + scope: Cluster + validation: + openAPIV3Schema: + description: PodTerminator represents a pod terminator. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata' + type: object + spec: + description: 'Specification of the ddesired behaviour of the pod terminator. + More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' + properties: + dryRun: + description: DryRun will set the killing in dryrun mode or not. + type: boolean + minimumInstances: + description: MinimumInstances is the number of minimum instances that + need to be alive. + format: int32 + type: integer + periodSeconds: + description: PeriodSeconds is how often (in seconds) to perform the + attack. + format: int32 + type: integer + selector: + additionalProperties: + type: string + description: Selector is how the target will be selected. + type: object + terminationPercent: + description: TerminationPercent is the percent of pods that will be + killed randomly. + format: int32 + type: integer + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/examples/pod-terminator-operator/operator/config.go b/examples/pod-terminator-operator/operator/config.go deleted file mode 100644 index b899667c..00000000 --- a/examples/pod-terminator-operator/operator/config.go +++ /dev/null @@ -1,11 +0,0 @@ -package operator - -import ( - "time" -) - -// Config is the controller configuration. -type Config struct { - // ResyncPeriod is the resync period of the operator. - ResyncPeriod time.Duration -} diff --git a/examples/pod-terminator-operator/operator/crd.go b/examples/pod-terminator-operator/operator/crd.go deleted file mode 100644 index 70afa19c..00000000 --- a/examples/pod-terminator-operator/operator/crd.go +++ /dev/null @@ -1,60 +0,0 @@ -package operator - -import ( - "github.com/spotahome/kooper/client/crd" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - - chaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" - podtermk8scli "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned" -) - -// podTerminatorCRD is the crd pod terminator. -type podTerminatorCRD struct { - crdCli crd.Interface - kubecCli kubernetes.Interface - podTermCli podtermk8scli.Interface -} - -func newPodTermiantorCRD(podTermCli podtermk8scli.Interface, crdCli crd.Interface, kubeCli kubernetes.Interface) *podTerminatorCRD { - return &podTerminatorCRD{ - crdCli: crdCli, - podTermCli: podTermCli, - kubecCli: kubeCli, - } -} - -// podTerminatorCRD satisfies resource.crd interface. -func (p *podTerminatorCRD) Initialize() error { - crd := crd.Conf{ - Kind: chaosv1alpha1.PodTerminatorKind, - NamePlural: chaosv1alpha1.PodTerminatorNamePlural, - ShortNames: chaosv1alpha1.PodTerminatorShortNames, - Group: chaosv1alpha1.SchemeGroupVersion.Group, - Version: chaosv1alpha1.SchemeGroupVersion.Version, - Scope: chaosv1alpha1.PodTerminatorScope, - Categories: []string{"chaos", "podterm"}, - } - - return p.crdCli.EnsurePresent(crd) -} - -// GetListerWatcher satisfies resource.crd interface (and retrieve.Retriever). -func (p *podTerminatorCRD) GetListerWatcher() cache.ListerWatcher { - return &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return p.podTermCli.ChaosV1alpha1().PodTerminators().List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return p.podTermCli.ChaosV1alpha1().PodTerminators().Watch(options) - }, - } -} - -// GetObject satisfies resource.crd interface (and retrieve.Retriever). -func (p *podTerminatorCRD) GetObject() runtime.Object { - return &chaosv1alpha1.PodTerminator{} -} diff --git a/examples/pod-terminator-operator/operator/factory.go b/examples/pod-terminator-operator/operator/factory.go deleted file mode 100644 index b2f4fd4f..00000000 --- a/examples/pod-terminator-operator/operator/factory.go +++ /dev/null @@ -1,27 +0,0 @@ -package operator - -import ( - "github.com/spotahome/kooper/client/crd" - "github.com/spotahome/kooper/operator" - "github.com/spotahome/kooper/operator/controller" - "k8s.io/client-go/kubernetes" - - podtermk8scli "github.com/spotahome/kooper/examples/pod-terminator-operator/client/k8s/clientset/versioned" - "github.com/spotahome/kooper/examples/pod-terminator-operator/log" -) - -// New returns pod terminator operator. -func New(cfg Config, podTermCli podtermk8scli.Interface, crdCli crd.Interface, kubeCli kubernetes.Interface, logger log.Logger) (operator.Operator, error) { - - // Create crd. - ptCRD := newPodTermiantorCRD(podTermCli, crdCli, kubeCli) - - // Create handler. - handler := newHandler(kubeCli, logger) - - // Create controller. - ctrl := controller.NewSequential(cfg.ResyncPeriod, handler, ptCRD, nil, logger) - - // Assemble CRD and controller to create the operator. - return operator.NewOperator(ptCRD, ctrl, logger), nil -} diff --git a/examples/pod-terminator-operator/operator/handler.go b/examples/pod-terminator-operator/operator/handler.go deleted file mode 100644 index 49df85ff..00000000 --- a/examples/pod-terminator-operator/operator/handler.go +++ /dev/null @@ -1,43 +0,0 @@ -package operator - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes" - - chaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" - "github.com/spotahome/kooper/examples/pod-terminator-operator/log" - "github.com/spotahome/kooper/examples/pod-terminator-operator/service/chaos" -) - -// Handler is the pod terminator handler that will handle the -// events received from kubernetes. -type handler struct { - chaosService chaos.Syncer - logger log.Logger -} - -// newHandler returns a new handler. -func newHandler(k8sCli kubernetes.Interface, logger log.Logger) *handler { - return &handler{ - chaosService: chaos.NewChaos(k8sCli, logger), - logger: logger, - } -} - -// Add will ensure that the required pod terminator is running. -func (h *handler) Add(_ context.Context, obj runtime.Object) error { - pt, ok := obj.(*chaosv1alpha1.PodTerminator) - if !ok { - return fmt.Errorf("%v is not a pod terminator object", obj.GetObjectKind()) - } - - return h.chaosService.EnsurePodTerminator(pt) -} - -// Delete will ensure the reuited pod terminator is not running. -func (h *handler) Delete(_ context.Context, name string) error { - return h.chaosService.DeletePodTerminator(name) -} diff --git a/examples/pod-terminator-operator/operator/operator.go b/examples/pod-terminator-operator/operator/operator.go new file mode 100644 index 00000000..d9bd2ad6 --- /dev/null +++ b/examples/pod-terminator-operator/operator/operator.go @@ -0,0 +1,112 @@ +package operator + +import ( + "context" + "fmt" + "time" + + "github.com/spotahome/kooper/v2/controller" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + + chaosv1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos/v1alpha1" + podtermk8scli "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/client/k8s/clientset/versioned" + "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/log" + "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/service/chaos" +) + +// Config is the controller configuration. +type Config struct { + // ResyncPeriod is the resync period of the operator. + ResyncPeriod time.Duration +} + +// New returns pod terminator operator. +func New(cfg Config, podTermCli podtermk8scli.Interface, kubeCli kubernetes.Interface, logger log.Logger) (controller.Controller, error) { + return controller.New(&controller.Config{ + Name: "pod-terminator", + Handler: newHandler(kubeCli, podTermCli, logger), + Retriever: newRetriever(podTermCli), + Logger: logger, + + ResyncInterval: cfg.ResyncPeriod, + }) +} + +func newRetriever(cli podtermk8scli.Interface) controller.Retriever { + return controller.MustRetrieverFromListerWatcher(&cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return cli.ChaosV1alpha1().PodTerminators().List(options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return cli.ChaosV1alpha1().PodTerminators().Watch(options) + }, + }) +} + +func newHandler(k8sCli kubernetes.Interface, ptCli podtermk8scli.Interface, logger log.Logger) controller.Handler { + const finalizer = "finalizer.chaos.spotahome.com/podKiller" + chaossvc := chaos.NewChaos(k8sCli, logger) + + return controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error { + pt, ok := obj.(*chaosv1alpha1.PodTerminator) + if !ok { + return fmt.Errorf("%v is not a pod terminator object", obj.GetObjectKind()) + } + + switch { + // Handle deletion and remove finalizer. + case !pt.DeletionTimestamp.IsZero() && stringPresentInSlice(pt.Finalizers, finalizer): + logger.Infof("handling pod termination deletion...") + err := chaossvc.DeletePodTerminator(pt.ObjectMeta.Name) + if err != nil { + return fmt.Errorf("could not handle PodTerminator deletion: %w", err) + } + + pt.Finalizers = removeStringFromSlice(pt.Finalizers, finalizer) + _, err = ptCli.ChaosV1alpha1().PodTerminators().Update(pt) + if err != nil { + return fmt.Errorf("could not update pod terminator: %w", err) + } + + return nil + + // Deletion already handled, don't do anything. + case !pt.DeletionTimestamp.IsZero() && !stringPresentInSlice(pt.Finalizers, finalizer): + logger.Infof("handling pod termination deletion already handled, skipping...") + return nil + + // Add finalizer to the object. + case pt.DeletionTimestamp.IsZero() && !stringPresentInSlice(pt.Finalizers, finalizer): + pt.Finalizers = append(pt.Finalizers, finalizer) + _, err := ptCli.ChaosV1alpha1().PodTerminators().Update(pt) + if err != nil { + return fmt.Errorf("could not update pod termiantor: %w", err) + } + } + + // Handle. + return chaossvc.EnsurePodTerminator(pt) + }) +} + +func stringPresentInSlice(ss []string, s string) bool { + for _, f := range ss { + if f == s { + return true + } + } + return false +} + +func removeStringFromSlice(ss []string, s string) []string { + for i, f := range ss { + if f == s { + return append(ss[:i], ss[i+1:]...) + } + } + return ss +} diff --git a/examples/pod-terminator-operator/service/chaos/chaos.go b/examples/pod-terminator-operator/service/chaos/chaos.go index e6f32465..ac7534f9 100644 --- a/examples/pod-terminator-operator/service/chaos/chaos.go +++ b/examples/pod-terminator-operator/service/chaos/chaos.go @@ -5,8 +5,8 @@ import ( "k8s.io/client-go/kubernetes" - chaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" - "github.com/spotahome/kooper/examples/pod-terminator-operator/log" + chaosv1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos/v1alpha1" + "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/log" ) // Syncer is the interface that every chaos service implementation @@ -59,7 +59,6 @@ func (c *Chaos) EnsurePodTerminator(pt *chaosv1alpha1.PodTerminator) error { pk = NewPodKiller(ptCopy, c.k8sCli, c.logger) c.reg.Store(pt.Name, pk) return pk.Start() - // TODO: garbage collection. } // DeletePodTerminator satisfies ChaosSyncer interface. diff --git a/examples/pod-terminator-operator/service/chaos/podkill.go b/examples/pod-terminator-operator/service/chaos/podkill.go index 29382ef9..69783161 100644 --- a/examples/pod-terminator-operator/service/chaos/podkill.go +++ b/examples/pod-terminator-operator/service/chaos/podkill.go @@ -13,8 +13,8 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" - chaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" - "github.com/spotahome/kooper/examples/pod-terminator-operator/log" + chaosv1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos/v1alpha1" + "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/log" ) // TimeWrapper is a wrapper around time so it can be mocked diff --git a/examples/pod-terminator-operator/service/chaos/podkill_test.go b/examples/pod-terminator-operator/service/chaos/podkill_test.go index 2cc95a99..8d777614 100644 --- a/examples/pod-terminator-operator/service/chaos/podkill_test.go +++ b/examples/pod-terminator-operator/service/chaos/podkill_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/spotahome/kooper/log" + "github.com/spotahome/kooper/v2/log" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -13,8 +13,8 @@ import ( "k8s.io/client-go/kubernetes/fake" kubetesting "k8s.io/client-go/testing" - chaosv1alpha1 "github.com/spotahome/kooper/examples/pod-terminator-operator/apis/chaos/v1alpha1" - "github.com/spotahome/kooper/examples/pod-terminator-operator/service/chaos" + chaosv1alpha1 "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/apis/chaos/v1alpha1" + "github.com/spotahome/kooper/v2/examples/pod-terminator-operator/service/chaos" ) type timeMock struct { diff --git a/examples/traced-controller/fakedservice.go b/examples/traced-controller/fakedservice.go deleted file mode 100644 index 76283619..00000000 --- a/examples/traced-controller/fakedservice.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "context" - "fmt" - "time" - - opentracing "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" -) - -type fakeService struct { - tracer opentracing.Tracer -} - -func (f *fakeService) makeOperation(ctx context.Context, opName string, duration time.Duration, failRandomly bool) (context.Context, error) { - - // Create the span. - pSpan := opentracing.SpanFromContext(ctx) - span := f.tracer.StartSpan(opName, opentracing.ChildOf(pSpan.Context())) - newCtx := opentracing.ContextWithSpan(ctx, span) - defer span.Finish() - - // Fail sometimes (crappy and fast version). - var err error - if time.Now().Nanosecond()%7 == 0 { - ext.Error.Set(span, true) - err = fmt.Errorf("randomly failed") - } - - time.Sleep(duration) - return newCtx, err -} diff --git a/examples/traced-controller/main.go b/examples/traced-controller/main.go deleted file mode 100644 index ac429f41..00000000 --- a/examples/traced-controller/main.go +++ /dev/null @@ -1,265 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "io" - "os" - "os/signal" - "path/filepath" - "syscall" - "time" - - opentracing "github.com/opentracing/opentracing-go" - jaeger "github.com/uber/jaeger-client-go" - jaegerconfig "github.com/uber/jaeger-client-go/config" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" - - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/operator/retrieve" -) - -// Important. Run a jaeger development instance to see the traces. -// -// docker run --rm -it -p5775:5775/udp -p6831:6831/udp -p6832:6832/udp -p5778:5778 -p16686:16686 -p14268:14268 -p9411:9411 jaegertracing/all-in-one:latest - -const ( - namespaceDef = "default" - controllerNameDef = "traced-pod-controller" - resyncIntervalSecondsDef = 120 -) - -// Flags are the flags of the program. -type Flags struct { - ControllerName string - ResyncIntervalSeconds int - Namespace string -} - -// NewFlags returns the flags of the commandline. -func NewFlags() *Flags { - flags := &Flags{} - fl := flag.NewFlagSet(os.Args[0], flag.ExitOnError) - - fl.IntVar(&flags.ResyncIntervalSeconds, "resync-interval", resyncIntervalSecondsDef, "resync seconds of the controller") - fl.StringVar(&flags.Namespace, "namespace", namespaceDef, "kubernetes namespace where the controller is running") - fl.StringVar(&flags.ControllerName, "controller-name", controllerNameDef, "controller name (service name)") - - fl.Parse(os.Args[1:]) - - return flags -} - -// Main runs the main application. -func Main() error { - // Flags - fl := NewFlags() - - // Initialize logger. - logger := &log.Std{} - - // Get k8s client. - k8scfg, err := rest.InClusterConfig() - if err != nil { - // No in cluster? letr's try locally - kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config") - k8scfg, err = clientcmd.BuildConfigFromFlags("", kubehome) - if err != nil { - return fmt.Errorf("error loading kubernetes configuration: %s", err) - } - } - k8scli, err := kubernetes.NewForConfig(k8scfg) - if err != nil { - return fmt.Errorf("error creating kubernetes client: %s", err) - } - - // Create AWS service. - aws, closer, err := createFakeService("aws") - if err != nil { - return err - } - defer closer.Close() - - // Create Redis service. - redis, closer, err := createFakeService("redis") - if err != nil { - return err - } - defer closer.Close() - - // Create Redis service. - github, closer, err := createFakeService("github") - if err != nil { - return err - } - defer closer.Close() - - // Create controller tracer. - tracer, closer, err := createTracer(fl.ControllerName) - if err != nil { - return err - } - defer closer.Close() - - // Create our retriever so the controller knows how to get/listen for pod events. - retr := &retrieve.Resource{ - Object: &corev1.Pod{}, - ListerWatcher: &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return k8scli.CoreV1().Pods("").List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return k8scli.CoreV1().Pods("").Watch(options) - }, - }, - } - - // Our domain logic that will print every add/sync/update and delete event we . - hand := &handler.HandlerFunc{ - AddFunc: func(ctx context.Context, obj runtime.Object) error { - pod := obj.(*corev1.Pod) - logger.Infof("Pod added: %s/%s", pod.Namespace, pod.Name) - - // Create span - pSpan := opentracing.SpanFromContext(ctx) - span := tracer.StartSpan("AddFunc", opentracing.ChildOf(pSpan.Context())) - defer span.Finish() - ctx = opentracing.ContextWithSpan(ctx, span) - - // Execute. - if _, err := redis.makeOperation(ctx, "getOrg", 10*time.Millisecond, true); err != nil { - return err - } - if _, err := github.makeOperation(ctx, "getOrg", 75*time.Millisecond, true); err != nil { - return err - } - - if _, err := redis.makeOperation(ctx, "getEc2List", 14*time.Millisecond, true); err != nil { - return err - } - - // AWS stuff - { - var newCtx context.Context - newCtx, err := aws.makeOperation(ctx, "ensureAWSResources", 120*time.Millisecond, true) - if err != nil { - return err - } - - if _, err := aws.makeOperation(newCtx, "getEc2List", 672*time.Millisecond, true); err != nil { - return err - } - - if _, err := aws.makeOperation(newCtx, "getALBs", 232*time.Millisecond, true); err != nil { - return err - } - if _, err := aws.makeOperation(newCtx, "linkSGInALB", 157*time.Millisecond, true); err != nil { - return err - } - } - - if _, err := redis.makeOperation(ctx, "storeResult", 34*time.Millisecond, true); err != nil { - return err - } - - return nil - }, - DeleteFunc: func(ctx context.Context, s string) error { - logger.Infof("Pod deleted: %s", s) - - // Create span - pSpan := opentracing.SpanFromContext(ctx) - span := tracer.StartSpan("DeleteFunc", opentracing.ChildOf(pSpan.Context())) - defer span.Finish() - ctx = opentracing.ContextWithSpan(ctx, span) - - // Execute. - if _, err := aws.makeOperation(ctx, "deleteCloudformationStack", 698*time.Millisecond, true); err != nil { - return err - } - if _, err := redis.makeOperation(ctx, "storeResult", 26*time.Millisecond, true); err != nil { - return err - } - - return nil - }, - } - - // Create the controller and run. - cfg := &controller.Config{ - Name: fl.ControllerName, - ProcessingJobRetries: 5, - ResyncInterval: time.Duration(fl.ResyncIntervalSeconds) * time.Second, - ConcurrentWorkers: 30, - } - ctrl := controller.New(cfg, hand, retr, nil, tracer, nil, logger) - stopC := make(chan struct{}) - errC := make(chan error) - go func() { - errC <- ctrl.Run(stopC) - }() - - sigC := make(chan os.Signal, 1) - signal.Notify(sigC, syscall.SIGTERM, syscall.SIGINT) - - select { - case err := <-errC: - if err != nil { - logger.Infof("controller finished with error: %s", err) - return err - } - logger.Infof("controller finished successfuly") - case s := <-sigC: - logger.Infof("signal %s received", s) - close(stopC) - } - - time.Sleep(5 * time.Second) - - return nil -} - -func createFakeService(service string) (*fakeService, io.Closer, error) { - t, cl, err := createTracer(service) - return &fakeService{ - tracer: t, - }, cl, err -} - -func createTracer(service string) (opentracing.Tracer, io.Closer, error) { - cfg := &jaegerconfig.Configuration{ - ServiceName: service, - Sampler: &jaegerconfig.SamplerConfig{ - Type: "const", - Param: 1, - }, - Reporter: &jaegerconfig.ReporterConfig{ - LogSpans: true, - }, - } - tracer, closer, err := cfg.NewTracer(jaegerconfig.Logger(jaeger.NullLogger)) - if err != nil { - return nil, nil, fmt.Errorf("cannot init Jaeger: %s", err) - } - return tracer, closer, nil -} - -func main() { - if err := Main(); err != nil { - fmt.Fprintf(os.Stderr, "error executing controller: %s", err) - os.Exit(1) - } - os.Exit(0) -} diff --git a/go.mod b/go.mod index 206e48ec..0a8370b0 100644 --- a/go.mod +++ b/go.mod @@ -1,96 +1,12 @@ -module github.com/spotahome/kooper - -// Dependencies we don't really need, except that kubernetes specifies them as v0.0.0 which confuses go.mod -//replace k8s.io/apiserver => k8s.io/apiserver kubernetes-1.15.6 -//replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver kubernetes-1.15.6 -//replace k8s.io/api => k8s.io/api kubernetes-1.15.6 -//replace k8s.io/component-base => k8s.io/component-base kubernetes-1.15.6 -//replace k8s.io/client-go => k8s.io/client-go kubernetes-1.15.6 -//replace k8s.io/kube-scheduler => k8s.io/kube-scheduler kubernetes-1.15.6 -//replace k8s.io/apimachinery => k8s.io/apimachinery kubernetes-1.15.6 -//replace k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers kubernetes-1.15.6 -//replace k8s.io/kubelet => k8s.io/kubelet kubernetes-1.15.6 -//replace k8s.io/cloud-provider => k8s.io/cloud-provider kubernetes-1.15.6 -//replace k8s.io/csi-translation-lib => k8s.io/csi-translation-lib kubernetes-1.15.6 -//replace k8s.io/cli-runtime => k8s.io/cli-runtime kubernetes-1.15.6 -//replace k8s.io/kube-aggregator => k8s.io/kube-aggregator kubernetes-1.15.6 -//replace k8s.io/sample-apiserver => k8s.io/sample-apiserver kubernetes-1.15.6 -//replace k8s.io/metrics => k8s.io/metrics kubernetes-1.15.6 -//replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap kubernetes-1.15.6 -//replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager kubernetes-1.15.6 -//replace k8s.io/kube-proxy => k8s.io/kube-proxy kubernetes-1.15.6 -//replace k8s.io/cri-api => k8s.io/cri-api kubernetes-1.15.6 -//replace k8s.io/code-generator => k8s.io/code-generator kubernetes-1.15.6 - -replace k8s.io/apiserver => k8s.io/apiserver v0.0.0-20191114102923-bf973bc1a46c - -replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20191114105316-e8706470940d - -replace k8s.io/api => k8s.io/api v0.0.0-20191114100237-2cd11237263f - -replace k8s.io/component-base => k8s.io/component-base v0.0.0-20191114102239-843ff05e8ff4 - -replace k8s.io/client-go => k8s.io/client-go v0.0.0-20191114101336-8cba805ad12d - -replace k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.0.0-20191114111147-29226eb67741 - -replace k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 - -replace k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.0.0-20191114112557-fb8eac6d1d79 - -replace k8s.io/kubelet => k8s.io/kubelet v0.0.0-20191114110913-8a0729368279 - -replace k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20191114111940-b2efa58ca04c - -replace k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.0.0-20191114112225-e438b10da852 - -replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20191114110057-22fabc8113ba - -replace k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20191114103707-3917fe134eab - -replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.0.0-20191114104325-4dc280b03897 - -replace k8s.io/metrics => k8s.io/metrics v0.0.0-20191114105745-bf91bab17669 - -replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.0.0-20191114111701-466976f32df4 - -replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.0.0-20191114111427-e269b4a0667c - -replace k8s.io/kube-proxy => k8s.io/kube-proxy v0.0.0-20191114110636-5b9a03eee945 - -replace k8s.io/cri-api => k8s.io/cri-api v0.0.0-20190817025403-3ae76f584e79 - -replace k8s.io/code-generator => k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b +module github.com/spotahome/kooper/v2 require ( - github.com/Pallinder/go-randomdata v0.0.0-20180329154440-dab270d296c6 - github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect - github.com/evanphx/json-patch v4.5.0+incompatible // indirect - github.com/gogo/protobuf v1.3.0 // indirect - github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect - github.com/googleapis/gnostic v0.3.1 // indirect - github.com/gophercloud/gophercloud v0.4.0 // indirect - github.com/hashicorp/golang-lru v0.5.3 // indirect - github.com/imdario/mergo v0.3.7 // indirect - github.com/opentracing/opentracing-go v1.1.0 - github.com/prometheus/client_golang v1.1.0 - github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.4.0 - github.com/uber-go/atomic v1.4.0 // indirect - github.com/uber/jaeger-client-go v2.19.0+incompatible - github.com/uber/jaeger-lib v2.2.0+incompatible // indirect - go.uber.org/atomic v1.4.0 // indirect - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect - golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 - golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/api v0.0.0 - k8s.io/apiextensions-apiserver v0.0.0 - k8s.io/apimachinery v0.0.0 - k8s.io/client-go v0.0.0 - k8s.io/klog v1.0.0 // indirect - k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d // indirect - k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6 // indirect + github.com/prometheus/client_golang v1.6.0 + github.com/sirupsen/logrus v1.6.0 + github.com/stretchr/testify v1.5.1 + k8s.io/api v0.17.4 + k8s.io/apimachinery v0.17.4 + k8s.io/client-go v0.17.4 ) -go 1.13 +go 1.14 diff --git a/go.sum b/go.sum index cbb4ae08..a9e0208a 100644 --- a/go.sum +++ b/go.sum @@ -1,93 +1,70 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtzYCrT+um/rrSQ1BfA= -github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/Pallinder/go-randomdata v0.0.0-20180329154440-dab270d296c6 h1:L1uKFY3oBXGjb5NAaD/3LUp2j9Ntuzp0WYKxUZhE7Bc= -github.com/Pallinder/go-randomdata v0.0.0-20180329154440-dab270d296c6/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 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/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-oidc v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda h1:NyywMz59neOoVRFDz+ccfKWxn784fiHMDnZSy6T+JXY= -github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= -github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -95,51 +72,63 @@ 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.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 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.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= -github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= -github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= -github.com/gophercloud/gophercloud v0.4.0 h1:4iXQnHF7LKOl7ncQsRibnUmfx/unxT3rLAniYRB8kQQ= -github.com/gophercloud/gophercloud v0.4.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= -github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.0.0-20141017032234-72f9bd7c4e0c/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= -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.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 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.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -151,51 +140,46 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c h1:Hww8mOyEKTeON4bZn7FrlLismspbPc1teNRUVH7wLQ8= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c h1:eSfnfIuwhxZyULg1NNuZycJcYkjYVGYe7FczwQReM6U= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3 h1:EooPXg51Tn+xmWPXJUGCnJhJSpeuMlBmfJVcqIRmmv8= -github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A= +github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 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/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -207,132 +191,126 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= -github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= -github.com/uber/jaeger-client-go v2.19.0+incompatible h1:pbwbYfHUoaase0oPQOdZ1GcaUjImYGimUXSQ/+8+Z8Q= -github.com/uber/jaeger-client-go v2.19.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= -github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68= -golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w= -golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 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/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= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -k8s.io/api v0.0.0-20191114100237-2cd11237263f h1:UfCVPkZrM+FAQ+c/cbThZTdXrOiuEYnVhzhIyrSqWQk= -k8s.io/api v0.0.0-20191114100237-2cd11237263f/go.mod h1:ceHJE/vDjU8jKnRV6Vqn/+vyZmC6NvOluInN+RhQkIs= -k8s.io/apiextensions-apiserver v0.0.0-20191114105316-e8706470940d h1:3h38p1tth7pLad/4U8pPTB7rAFIvlbzBM5Wbo8ne8is= -k8s.io/apiextensions-apiserver v0.0.0-20191114105316-e8706470940d/go.mod h1:z6StKBoP26Yie4MwJIQ6YDY70JC6KCeRYsKNU3DygEA= -k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 h1:GYWOVyO+ZU+YK01nyPiAwB/fQrkxysXwkjbSpIIHdN4= -k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762/go.mod h1:Xc10RHc1U+F/e9GCloJ8QAeCGevSVP5xhOhqlE+e1kM= -k8s.io/apiserver v0.0.0-20191114102923-bf973bc1a46c/go.mod h1:2XZVh9YO4VVxJEw9xiCdAQ1thGTinWc6uZvQiNQC6VI= -k8s.io/client-go v0.0.0-20191114101336-8cba805ad12d h1:waFh/beHLU2QHqkvROghucKaVw1ZP9eCu6eGYL+R04U= -k8s.io/client-go v0.0.0-20191114101336-8cba805ad12d/go.mod h1:bfRZpiGteZXHxZtDHXTU6b4PBZyXuOc76l9DBv1ASKA= -k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b/go.mod h1:G8bQwmHm2eafm5bgtX67XDZQ8CWKSGu9DekI+yN4Y5I= -k8s.io/component-base v0.0.0-20191114102239-843ff05e8ff4/go.mod h1:zT8T6A3K4wLlbQkLUC62skjmWoiNJ9B8WUQj3KIvcrQ= -k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.17.4 h1:HbwOhDapkguO8lTAE8OX3hdF2qp8GtpC9CW/MQATXXo= +k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA= +k8s.io/apimachinery v0.17.4 h1:UzM+38cPUJnzqSQ+E1PY4YxMHIzQyCg29LOoGfo79Zw= +k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/client-go v0.17.4 h1:VVdVbpTY70jiNHS1eiFkUt7ZIJX3txd29nDxxXH4en8= +k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d h1:Xpe6sK+RY4ZgCTyZ3y273UmFmURhjtoJiwOMbQsXitY= -k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= -k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6 h1:p0Ai3qVtkbCG/Af26dBmU0E1W58NID3hSSh7cMyylpM= -k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= -sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/hack/scripts/check.sh b/hack/scripts/check.sh new file mode 100755 index 00000000..532753e8 --- /dev/null +++ b/hack/scripts/check.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh + +set -o errexit +set -o nounset + +golangci-lint run -E goimports --timeout 3m \ No newline at end of file diff --git a/hack/scripts/integration-test-kind.sh b/hack/scripts/integration-test-kind.sh index 80e0fc15..a61841ab 100755 --- a/hack/scripts/integration-test-kind.sh +++ b/hack/scripts/integration-test-kind.sh @@ -3,7 +3,7 @@ set -o errexit set -o nounset -KUBERNETES_VERSION=v${KUBERNETES_VERSION:-1.15.6} +KUBERNETES_VERSION=v${KUBERNETES_VERSION:-1.15.7} current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PREVIOUS_KUBECTL_CONTEXT=$(kubectl config current-context) || PREVIOUS_KUBECTL_CONTEXT="" diff --git a/hack/scripts/unit-test.sh b/hack/scripts/unit-test.sh index fc016643..b3b3a088 100755 --- a/hack/scripts/unit-test.sh +++ b/hack/scripts/unit-test.sh @@ -3,4 +3,5 @@ set -o errexit set -o nounset -go test `go list ./... | grep -v vendor` -v \ No newline at end of file +go test -race -coverprofile=.test_coverage.txt ./... +go tool cover -func=.test_coverage.txt | tail -n1 | awk '{print "Total test coverage: " $3}' \ No newline at end of file diff --git a/log/log.go b/log/log.go index 10bc6e4f..fcc790ec 100644 --- a/log/log.go +++ b/log/log.go @@ -5,36 +5,78 @@ import ( "log" ) +// KV is a helper type for structured logging fields usage. +type KV map[string]interface{} + // Logger is the interface that the loggers used by the library will use. type Logger interface { Infof(format string, args ...interface{}) Warningf(format string, args ...interface{}) Errorf(format string, args ...interface{}) + Debugf(format string, args ...interface{}) + WithKV(KV) Logger } -// Dummy logger doesn't log anything -var Dummy = &dummy{} +// Dummy logger doesn't log anything. +const Dummy = dummy(0) -type dummy struct{} +type dummy int -func (d *dummy) Infof(format string, args ...interface{}) {} -func (d *dummy) Warningf(format string, args ...interface{}) {} -func (d *dummy) Errorf(format string, args ...interface{}) {} +func (d dummy) Infof(format string, args ...interface{}) {} +func (d dummy) Warningf(format string, args ...interface{}) {} +func (d dummy) Errorf(format string, args ...interface{}) {} +func (d dummy) Debugf(format string, args ...interface{}) {} +func (d dummy) WithKV(KV) Logger { return d } // Std is a wrapper for go standard library logger. -type Std struct{} +type std struct { + debug bool + fields map[string]interface{} +} + +// NewStd returns a Logger implementation with the standard logger. +func NewStd(debug bool) Logger { + return std{ + debug: debug, + fields: map[string]interface{}{}, + } +} + +func (s std) logWithPrefix(prefix, format string, kv map[string]interface{}, args ...interface{}) { -func (s *Std) logWithPrefix(prefix, format string, args ...interface{}) { - format = fmt.Sprintf("%s %s", prefix, format) - log.Printf(format, args...) + msgFmt := "" + if len(kv) == 0 { + msgFmt = fmt.Sprintf("%s\t%s", prefix, format) + } else { + msgFmt = fmt.Sprintf("%s\t%s\t\t%v", prefix, format, kv) + } + + log.Printf(msgFmt, args...) } -func (s *Std) Infof(format string, args ...interface{}) { - s.logWithPrefix("[INFO]", format, args...) +func (s std) Infof(format string, args ...interface{}) { + s.logWithPrefix("[INFO]", format, s.fields, args...) +} +func (s std) Warningf(format string, args ...interface{}) { + s.logWithPrefix("[WARN]", format, s.fields, args...) +} +func (s std) Errorf(format string, args ...interface{}) { + s.logWithPrefix("[ERROR]", format, s.fields, args...) } -func (s *Std) Warningf(format string, args ...interface{}) { - s.logWithPrefix("[WARN]", format, args...) +func (s std) Debugf(format string, args ...interface{}) { + if s.debug { + s.logWithPrefix("[DEBUG]", format, s.fields, args...) + } } -func (s *Std) Errorf(format string, args ...interface{}) { - s.logWithPrefix("[ERROR]", format, args...) + +func (s std) WithKV(kv KV) Logger { + kvs := map[string]interface{}{} + for k, v := range s.fields { + kvs[k] = v + } + for k, v := range kv { + kvs[k] = v + } + + return std{debug: s.debug, fields: kvs} } diff --git a/log/logrus/logrus.go b/log/logrus/logrus.go new file mode 100644 index 00000000..4209a8d8 --- /dev/null +++ b/log/logrus/logrus.go @@ -0,0 +1,21 @@ +package logrus + +import ( + "github.com/sirupsen/logrus" + + "github.com/spotahome/kooper/v2/log" +) + +type logger struct { + *logrus.Entry +} + +// New returns a new log.Logger for a logrus implementation. +func New(l *logrus.Entry) log.Logger { + return logger{Entry: l} +} + +func (l logger) WithKV(kv log.KV) log.Logger { + newLogger := l.Entry.WithFields(logrus.Fields(kv)) + return New(newLogger) +} diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go new file mode 100644 index 00000000..6f659938 --- /dev/null +++ b/metrics/prometheus/prometheus.go @@ -0,0 +1,107 @@ +package prometheus + +import ( + "context" + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/spotahome/kooper/v2/controller" +) + +const ( + promNamespace = "kooper" + promControllerSubsystem = "controller" +) + +// Config is the Recorder Config. +type Config struct { + // Registerer is a prometheus registerer, e.g: prometheus.Registry. + // By default will use Prometheus default registry. + Registerer prometheus.Registerer + // InQueueBuckets sets custom buckets for the duration/latency items in queue metrics. + // Check https://godoc.org/github.com/prometheus/client_golang/prometheus#pkg-variables + InQueueBuckets []float64 + // ProcessingBuckets sets custom buckets for the duration/latency processing metrics. + // Check https://godoc.org/github.com/prometheus/client_golang/prometheus#pkg-variables + ProcessingBuckets []float64 +} + +func (c *Config) defaults() { + if c.Registerer == nil { + c.Registerer = prometheus.DefaultRegisterer + } + + if c.InQueueBuckets == nil || len(c.InQueueBuckets) == 0 { + c.InQueueBuckets = prometheus.DefBuckets + } + + if c.ProcessingBuckets == nil || len(c.ProcessingBuckets) == 0 { + c.ProcessingBuckets = prometheus.DefBuckets + } +} + +// Recorder implements the metrics recording in a prometheus registry. +type Recorder struct { + queuedEventsTotal *prometheus.CounterVec + inQueueEventDuration *prometheus.HistogramVec + processedEventDuration *prometheus.HistogramVec +} + +// New returns a new Prometheus implementaiton for a metrics recorder. +func New(cfg Config) *Recorder { + cfg.defaults() + + r := &Recorder{ + queuedEventsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: promNamespace, + Subsystem: promControllerSubsystem, + Name: "queued_events_total", + Help: "Total number of events queued.", + }, []string{"controller", "requeue"}), + + inQueueEventDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: promNamespace, + Subsystem: promControllerSubsystem, + Name: "event_in_queue_duration_seconds", + Help: "The duration of an event in the queue.", + Buckets: cfg.InQueueBuckets, + }, []string{"controller"}), + + processedEventDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: promNamespace, + Subsystem: promControllerSubsystem, + Name: "processed_event_duration_seconds", + Help: "The duration for an event to be processed.", + Buckets: cfg.ProcessingBuckets, + }, []string{"controller", "success"}), + } + + // Register metrics. + cfg.Registerer.MustRegister( + r.queuedEventsTotal, + r.inQueueEventDuration, + r.processedEventDuration) + + return r +} + +// IncResourceEventQueued satisfies controller.MetricsRecorder interface. +func (r Recorder) IncResourceEventQueued(ctx context.Context, controller string, isRequeue bool) { + r.queuedEventsTotal.WithLabelValues(controller, strconv.FormatBool(isRequeue)).Inc() +} + +// ObserveResourceInQueueDuration satisfies controller.MetricsRecorder interface. +func (r Recorder) ObserveResourceInQueueDuration(ctx context.Context, controller string, queuedAt time.Time) { + r.inQueueEventDuration.WithLabelValues(controller). + Observe(time.Since(queuedAt).Seconds()) +} + +// ObserveResourceProcessingDuration satisfies controller.MetricsRecorder interface. +func (r Recorder) ObserveResourceProcessingDuration(ctx context.Context, controller string, success bool, startProcessingAt time.Time) { + r.processedEventDuration.WithLabelValues(controller, strconv.FormatBool(success)). + Observe(time.Since(startProcessingAt).Seconds()) +} + +// Check interfaces implementation. +var _ controller.MetricsRecorder = &Recorder{} diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go new file mode 100644 index 00000000..0fbbb2e0 --- /dev/null +++ b/metrics/prometheus/prometheus_test.go @@ -0,0 +1,213 @@ +package prometheus_test + +import ( + "context" + "io/ioutil" + "net/http/httptest" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/stretchr/testify/assert" + + kooperprometheus "github.com/spotahome/kooper/v2/metrics/prometheus" +) + +func TestPrometheusRecorder(t *testing.T) { + tests := map[string]struct { + cfg kooperprometheus.Config + addMetrics func(*kooperprometheus.Recorder) + expMetrics []string + }{ + "Incremeneting the total queued resource events should record the metrics.": { + addMetrics: func(r *kooperprometheus.Recorder) { + ctx := context.TODO() + r.IncResourceEventQueued(ctx, "ctrl1", false) + r.IncResourceEventQueued(ctx, "ctrl1", false) + r.IncResourceEventQueued(ctx, "ctrl2", false) + r.IncResourceEventQueued(ctx, "ctrl3", true) + r.IncResourceEventQueued(ctx, "ctrl3", true) + r.IncResourceEventQueued(ctx, "ctrl3", false) + }, + expMetrics: []string{ + `kooper_controller_queued_events_total{controller="ctrl1",requeue="false"} 2`, + `kooper_controller_queued_events_total{controller="ctrl2",requeue="false"} 1`, + `kooper_controller_queued_events_total{controller="ctrl3",requeue="false"} 1`, + `kooper_controller_queued_events_total{controller="ctrl3",requeue="true"} 2`, + }, + }, + + "Observing the duration in queue of events should record the metrics.": { + addMetrics: func(r *kooperprometheus.Recorder) { + ctx := context.TODO() + t0 := time.Now() + r.ObserveResourceInQueueDuration(ctx, "ctrl1", t0.Add(-3*time.Second)) + r.ObserveResourceInQueueDuration(ctx, "ctrl1", t0.Add(-280*time.Millisecond)) + r.ObserveResourceInQueueDuration(ctx, "ctrl2", t0.Add(-7*time.Second)) + r.ObserveResourceInQueueDuration(ctx, "ctrl2", t0.Add(-35*time.Millisecond)) + r.ObserveResourceInQueueDuration(ctx, "ctrl2", t0.Add(-770*time.Millisecond)) + r.ObserveResourceInQueueDuration(ctx, "ctrl2", t0.Add(-17*time.Millisecond)) + }, + expMetrics: []string{ + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="0.005"} 0`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="0.01"} 0`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="0.025"} 0`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="0.05"} 0`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="0.1"} 0`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="0.25"} 0`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="0.5"} 1`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="1"} 1`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="2.5"} 1`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="5"} 2`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="10"} 2`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="+Inf"} 2`, + `kooper_controller_event_in_queue_duration_seconds_count{controller="ctrl1"} 2`, + + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="0.005"} 0`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="0.01"} 0`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="0.025"} 1`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="0.05"} 2`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="0.1"} 2`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="0.25"} 2`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="0.5"} 2`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="1"} 3`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="2.5"} 3`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="5"} 3`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="10"} 4`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl2",le="+Inf"} 4`, + `kooper_controller_event_in_queue_duration_seconds_count{controller="ctrl2"} 4`, + }, + }, + + "Observing the duration in queue of events should record the metrics (Custom buckets).": { + cfg: kooperprometheus.Config{ + InQueueBuckets: []float64{10, 20, 30, 50}, + }, + addMetrics: func(r *kooperprometheus.Recorder) { + ctx := context.TODO() + t0 := time.Now() + r.ObserveResourceInQueueDuration(ctx, "ctrl1", t0.Add(-6*time.Second)) + r.ObserveResourceInQueueDuration(ctx, "ctrl1", t0.Add(-12*time.Second)) + r.ObserveResourceInQueueDuration(ctx, "ctrl1", t0.Add(-25*time.Second)) + r.ObserveResourceInQueueDuration(ctx, "ctrl1", t0.Add(-60*time.Second)) + r.ObserveResourceInQueueDuration(ctx, "ctrl1", t0.Add(-70*time.Second)) + + }, + expMetrics: []string{ + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="10"} 1`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="20"} 2`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="30"} 3`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="50"} 3`, + `kooper_controller_event_in_queue_duration_seconds_bucket{controller="ctrl1",le="+Inf"} 5`, + `kooper_controller_event_in_queue_duration_seconds_count{controller="ctrl1"} 5`, + }, + }, + + "Observing the duration of processing events should record the metrics.": { + addMetrics: func(r *kooperprometheus.Recorder) { + ctx := context.TODO() + t0 := time.Now() + r.ObserveResourceProcessingDuration(ctx, "ctrl1", true, t0.Add(-3*time.Second)) + r.ObserveResourceProcessingDuration(ctx, "ctrl1", true, t0.Add(-280*time.Millisecond)) + r.ObserveResourceProcessingDuration(ctx, "ctrl2", true, t0.Add(-7*time.Second)) + r.ObserveResourceProcessingDuration(ctx, "ctrl2", false, t0.Add(-35*time.Millisecond)) + r.ObserveResourceProcessingDuration(ctx, "ctrl2", true, t0.Add(-770*time.Millisecond)) + r.ObserveResourceProcessingDuration(ctx, "ctrl2", false, t0.Add(-17*time.Millisecond)) + }, + expMetrics: []string{ + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="0.005"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="0.01"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="0.025"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="0.05"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="0.1"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="0.25"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="0.5"} 1`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="1"} 1`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="2.5"} 1`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="5"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="10"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="+Inf"} 2`, + `kooper_controller_processed_event_duration_seconds_count{controller="ctrl1",success="true"} 2`, + + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="0.005"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="0.01"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="0.025"} 1`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="0.05"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="0.1"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="0.25"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="0.5"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="1"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="2.5"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="5"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="10"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="false",le="+Inf"} 2`, + `kooper_controller_processed_event_duration_seconds_count{controller="ctrl2",success="false"} 2`, + + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="0.005"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="0.01"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="0.025"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="0.05"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="0.1"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="0.25"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="0.5"} 0`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="1"} 1`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="2.5"} 1`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="5"} 1`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="10"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl2",success="true",le="+Inf"} 2`, + `kooper_controller_processed_event_duration_seconds_count{controller="ctrl2",success="true"} 2`, + }, + }, + + "Observing the duration of processing events should record the metrics (Custom buckets).": { + cfg: kooperprometheus.Config{ + ProcessingBuckets: []float64{10, 20, 30, 50}, + }, + addMetrics: func(r *kooperprometheus.Recorder) { + ctx := context.TODO() + t0 := time.Now() + r.ObserveResourceProcessingDuration(ctx, "ctrl1", true, t0.Add(-6*time.Second)) + r.ObserveResourceProcessingDuration(ctx, "ctrl1", true, t0.Add(-12*time.Second)) + r.ObserveResourceProcessingDuration(ctx, "ctrl1", true, t0.Add(-25*time.Second)) + r.ObserveResourceProcessingDuration(ctx, "ctrl1", true, t0.Add(-60*time.Second)) + r.ObserveResourceProcessingDuration(ctx, "ctrl1", true, t0.Add(-70*time.Second)) + }, + expMetrics: []string{ + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="10"} 1`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="20"} 2`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="30"} 3`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="50"} 3`, + `kooper_controller_processed_event_duration_seconds_bucket{controller="ctrl1",success="true",le="+Inf"} 5`, + `kooper_controller_processed_event_duration_seconds_count{controller="ctrl1",success="true"} 5`, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + // Create a new prometheus empty registry and a kooper prometheus recorder. + reg := prometheus.NewRegistry() + test.cfg.Registerer = reg + m := kooperprometheus.New(test.cfg) + + // Add desired metrics + test.addMetrics(m) + + // Ask prometheus for the metrics + h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) + r := httptest.NewRequest("GET", "/metrics", nil) + w := httptest.NewRecorder() + h.ServeHTTP(w, r) + resp := w.Result() + + // Check all metrics are present. + body, _ := ioutil.ReadAll(resp.Body) + for _, expMetric := range test.expMetrics { + assert.Contains(string(body), expMetric, "metric not present on the result of metrics service") + } + }) + } +} diff --git a/mocks/operator/controller/Controller.go b/mocks/controller/Controller.go similarity index 100% rename from mocks/operator/controller/Controller.go rename to mocks/controller/Controller.go diff --git a/mocks/controller/Handler.go b/mocks/controller/Handler.go new file mode 100644 index 00000000..ebe15dfc --- /dev/null +++ b/mocks/controller/Handler.go @@ -0,0 +1,30 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package controller + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// Handler is an autogenerated mock type for the Handler type +type Handler struct { + mock.Mock +} + +// Handle provides a mock function with given fields: _a0, _a1 +func (_m *Handler) Handle(_a0 context.Context, _a1 runtime.Object) error { + ret := _m.Called(_a0, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, runtime.Object) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/mocks/doc.go b/mocks/doc.go index e994bb9f..fab02d31 100644 --- a/mocks/doc.go +++ b/mocks/doc.go @@ -2,12 +2,11 @@ Package mocks will have all the mocks of the library, we'll try to use mocking using blackbox testing and integration tests whenever is possible. */ -package mocks // import "github.com/spotahome/kooper/mocks" +package mocks // import "github.com/spotahome/kooper/v2/mocks" // Operator tooling mocks. -//go:generate mockery -output ./operator/resource -outpkg resource -dir ../operator/resource -name CRD -//go:generate mockery -output ./operator/controller -outpkg controller -dir ../operator/controller -name Controller -//go:generate mockery -output ./operator/handler -outpkg handler -dir ../operator/handler -name Handler +//go:generate mockery -output ./controller -outpkg controller -dir ../controller -name Controller +//go:generate mockery -output ./controller -outpkg controller -dir ../controller -name Handler // Wrappers mocks //go:generate mockery -output ./wrapper/time -outpkg time -dir ../wrapper/time -name Time diff --git a/mocks/operator/handler/Handler.go b/mocks/operator/handler/Handler.go deleted file mode 100644 index ed173713..00000000 --- a/mocks/operator/handler/Handler.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package handler - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// Handler is an autogenerated mock type for the Handler type -type Handler struct { - mock.Mock -} - -// Add provides a mock function with given fields: _a0, _a1 -func (_m *Handler) Add(_a0 context.Context, _a1 runtime.Object) error { - ret := _m.Called(_a0, _a1) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, runtime.Object) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Delete provides a mock function with given fields: _a0, _a1 -func (_m *Handler) Delete(_a0 context.Context, _a1 string) error { - ret := _m.Called(_a0, _a1) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/mocks/operator/resource/CRD.go b/mocks/operator/resource/CRD.go deleted file mode 100644 index e0cbc3ef..00000000 --- a/mocks/operator/resource/CRD.go +++ /dev/null @@ -1,61 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package resource - -import ( - mock "github.com/stretchr/testify/mock" - cache "k8s.io/client-go/tools/cache" - - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// CRD is an autogenerated mock type for the CRD type -type CRD struct { - mock.Mock -} - -// GetListerWatcher provides a mock function with given fields: -func (_m *CRD) GetListerWatcher() cache.ListerWatcher { - ret := _m.Called() - - var r0 cache.ListerWatcher - if rf, ok := ret.Get(0).(func() cache.ListerWatcher); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(cache.ListerWatcher) - } - } - - return r0 -} - -// GetObject provides a mock function with given fields: -func (_m *CRD) GetObject() runtime.Object { - ret := _m.Called() - - var r0 runtime.Object - if rf, ok := ret.Get(0).(func() runtime.Object); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(runtime.Object) - } - } - - return r0 -} - -// Initialize provides a mock function with given fields: -func (_m *CRD) Initialize() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/monitoring/metrics/dummy.go b/monitoring/metrics/dummy.go deleted file mode 100644 index 11cdbe39..00000000 --- a/monitoring/metrics/dummy.go +++ /dev/null @@ -1,17 +0,0 @@ -package metrics - -import "time" - -// Dummy is a dummy stats recorder. -var Dummy = &dummy{} - -type dummy struct{} - -func (*dummy) IncResourceEventQueued(_ string, _ EventType) { -} -func (*dummy) IncResourceEventProcessed(_ string, _ EventType) { -} -func (*dummy) IncResourceEventProcessedError(_ string, _ EventType) { -} -func (*dummy) ObserveDurationResourceEventProcessed(_ string, _ EventType, _ time.Time) { -} diff --git a/monitoring/metrics/metrics.go b/monitoring/metrics/metrics.go deleted file mode 100644 index f4726eef..00000000 --- a/monitoring/metrics/metrics.go +++ /dev/null @@ -1,27 +0,0 @@ -package metrics - -import "time" - -// EventType is the event type handled by the controller. -type EventType string - -const ( - //AddEvent is the add event. - AddEvent EventType = "add" - // DeleteEvent is the delete event. - DeleteEvent EventType = "delete" - // RequeueEvent is a requeued event (unknown state when handling again). - RequeueEvent EventType = "requeue" -) - -// Recorder knows how to record metrics all over the application. -type Recorder interface { - // IncResourceEvent increments in one the metric records of a queued event. - IncResourceEventQueued(controller string, eventType EventType) - // IncResourceEventProcessed increments in one the metric records processed event. - IncResourceEventProcessed(controller string, eventType EventType) - // IncResourceEventProcessedError increments in one the metric records of a processed event in error. - IncResourceEventProcessedError(controller string, eventType EventType) - // ObserveDurationResourceEventProcessed measures the duration it took to process a event. - ObserveDurationResourceEventProcessed(controller string, eventType EventType, start time.Time) -} diff --git a/monitoring/metrics/prometheus.go b/monitoring/metrics/prometheus.go deleted file mode 100644 index f241176b..00000000 --- a/monitoring/metrics/prometheus.go +++ /dev/null @@ -1,100 +0,0 @@ -package metrics - -import ( - "time" - - "github.com/prometheus/client_golang/prometheus" -) - -const ( - promNamespace = "kooper" - promControllerSubsystem = "controller" -) - -// Prometheus implements the metrics recording in a prometheus registry. -type Prometheus struct { - // Metrics - queuedEvents *prometheus.CounterVec - processedEvents *prometheus.CounterVec - processedEventErrors *prometheus.CounterVec - processedEventDuration *prometheus.HistogramVec - - reg prometheus.Registerer -} - -// NewPrometheus returns a new Prometheus metrics backend with metrics prefixed by the namespace. -func NewPrometheus(registry prometheus.Registerer) *Prometheus { - return NewPrometheusWithBuckets(prometheus.DefBuckets, registry) -} - -// NewPrometheusWithBuckets returns a new Prometheus metrics backend with metrics prefixed by the -// namespace and with custom buckets for the duration/latency metrics. This kind should be used when -// the default buckets don't work. This could happen when the time to process an event is not on the -// range of 5ms-10s duration. -// Check https://godoc.org/github.com/prometheus/client_golang/prometheus#pkg-variables -func NewPrometheusWithBuckets(buckets []float64, registry prometheus.Registerer) *Prometheus { - p := &Prometheus{ - queuedEvents: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: promNamespace, - Subsystem: promControllerSubsystem, - Name: "queued_events_total", - Help: "Total number of events queued.", - }, []string{"controller", "type"}), - - processedEvents: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: promNamespace, - Subsystem: promControllerSubsystem, - Name: "processed_events_total", - Help: "Total number of successfuly processed events.", - }, []string{"controller", "type"}), - - processedEventErrors: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: promNamespace, - Subsystem: promControllerSubsystem, - Name: "processed_event_errors_total", - Help: "Total number of errors processing events.", - }, []string{"controller", "type"}), - - processedEventDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: promNamespace, - Subsystem: promControllerSubsystem, - Name: "processed_event_duration_seconds", - Help: "The duration for a successful event to be processed.", - Buckets: buckets, - }, []string{"controller", "type"}), - reg: registry, - } - - p.registerMetrics() - return p -} - -func (p *Prometheus) registerMetrics() { - p.reg.MustRegister( - p.queuedEvents, - p.processedEvents, - p.processedEventErrors, - p.processedEventDuration) - -} - -// IncResourceEventQueued satisfies metrics.Recorder interface. -func (p *Prometheus) IncResourceEventQueued(controller string, eventType EventType) { - p.queuedEvents.WithLabelValues(controller, string(eventType)).Inc() -} - -// IncResourceEventProcessed satisfies metrics.Recorder interface. -func (p *Prometheus) IncResourceEventProcessed(controller string, eventType EventType) { - p.processedEvents.WithLabelValues(controller, string(eventType)).Inc() -} - -// IncResourceEventProcessedError satisfies metrics.Recorder interface. -func (p *Prometheus) IncResourceEventProcessedError(controller string, eventType EventType) { - p.processedEventErrors.WithLabelValues(controller, string(eventType)).Inc() -} - -// ObserveDurationResourceEventProcessed satisfies metrics.Recorder interface. -func (p *Prometheus) ObserveDurationResourceEventProcessed(controller string, eventType EventType, start time.Time) { - secs := time.Now().Sub(start).Seconds() - p.processedEventDuration.WithLabelValues(controller, string(eventType)).Observe(secs) -} diff --git a/monitoring/metrics/prometheus_test.go b/monitoring/metrics/prometheus_test.go deleted file mode 100644 index 251bd534..00000000 --- a/monitoring/metrics/prometheus_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package metrics_test - -import ( - "io/ioutil" - "net/http/httptest" - "testing" - "time" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/stretchr/testify/assert" - - "github.com/spotahome/kooper/monitoring/metrics" -) - -func TestPrometheusMetrics(t *testing.T) { - controller := "test" - - tests := []struct { - name string - addMetrics func(*metrics.Prometheus) - expMetrics []string - expCode int - }{ - { - name: "Incrementing different kind of queued events should measure the queued events counter", - addMetrics: func(p *metrics.Prometheus) { - p.IncResourceEventQueued(controller, metrics.AddEvent) - p.IncResourceEventQueued(controller, metrics.AddEvent) - p.IncResourceEventQueued(controller, metrics.AddEvent) - p.IncResourceEventQueued(controller, metrics.AddEvent) - p.IncResourceEventQueued(controller, metrics.DeleteEvent) - p.IncResourceEventQueued(controller, metrics.DeleteEvent) - p.IncResourceEventQueued(controller, metrics.DeleteEvent) - }, - expMetrics: []string{ - `kooper_controller_queued_events_total{controller="test",type="add"} 4`, - `kooper_controller_queued_events_total{controller="test",type="delete"} 3`, - }, - expCode: 200, - }, - { - name: "Incrementing different kind of processed events should measure the processed events counter", - addMetrics: func(p *metrics.Prometheus) { - p.IncResourceEventProcessed(controller, metrics.AddEvent) - p.IncResourceEventProcessedError(controller, metrics.AddEvent) - p.IncResourceEventProcessedError(controller, metrics.AddEvent) - p.IncResourceEventProcessed(controller, metrics.DeleteEvent) - p.IncResourceEventProcessed(controller, metrics.DeleteEvent) - p.IncResourceEventProcessed(controller, metrics.DeleteEvent) - p.IncResourceEventProcessedError(controller, metrics.DeleteEvent) - p.IncResourceEventProcessedError(controller, metrics.DeleteEvent) - p.IncResourceEventProcessedError(controller, metrics.DeleteEvent) - p.IncResourceEventProcessedError(controller, metrics.DeleteEvent) - - }, - expMetrics: []string{ - `kooper_controller_processed_events_total{controller="test",type="add"} 1`, - `kooper_controller_processed_event_errors_total{controller="test",type="add"} 2`, - `kooper_controller_processed_events_total{controller="test",type="delete"} 3`, - `kooper_controller_processed_event_errors_total{controller="test",type="delete"} 4`, - }, - expCode: 200, - }, - { - name: "Measuring the duration of processed events return the correct buckets.", - addMetrics: func(p *metrics.Prometheus) { - now := time.Now() - p.ObserveDurationResourceEventProcessed(controller, metrics.AddEvent, now.Add(-2*time.Millisecond)) - p.ObserveDurationResourceEventProcessed(controller, metrics.AddEvent, now.Add(-3*time.Millisecond)) - p.ObserveDurationResourceEventProcessed(controller, metrics.AddEvent, now.Add(-11*time.Millisecond)) - p.ObserveDurationResourceEventProcessed(controller, metrics.AddEvent, now.Add(-280*time.Millisecond)) - p.ObserveDurationResourceEventProcessed(controller, metrics.AddEvent, now.Add(-1*time.Second)) - p.ObserveDurationResourceEventProcessed(controller, metrics.AddEvent, now.Add(-5*time.Second)) - p.ObserveDurationResourceEventProcessed(controller, metrics.DeleteEvent, now.Add(-110*time.Millisecond)) - p.ObserveDurationResourceEventProcessed(controller, metrics.DeleteEvent, now.Add(-560*time.Millisecond)) - p.ObserveDurationResourceEventProcessed(controller, metrics.DeleteEvent, now.Add(-4*time.Second)) - p.ObserveDurationResourceEventProcessed(controller, metrics.DeleteEvent, now.Add(-7*time.Second)) - p.ObserveDurationResourceEventProcessed(controller, metrics.DeleteEvent, now.Add(-12*time.Second)) - p.ObserveDurationResourceEventProcessed(controller, metrics.DeleteEvent, now.Add(-30*time.Second)) - }, - expMetrics: []string{ - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="0.005"} 2`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="0.01"} 2`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="0.025"} 3`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="0.05"} 3`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="0.1"} 3`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="0.25"} 3`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="0.5"} 4`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="1"} 4`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="2.5"} 5`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="5"} 5`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="10"} 6`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="add",le="+Inf"} 6`, - `kooper_controller_processed_event_duration_seconds_count{controller="test",type="add"} 6`, - - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="0.005"} 0`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="0.01"} 0`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="0.025"} 0`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="0.05"} 0`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="0.1"} 0`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="0.25"} 1`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="0.5"} 1`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="1"} 2`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="2.5"} 2`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="5"} 3`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="10"} 4`, - `kooper_controller_processed_event_duration_seconds_bucket{controller="test",type="delete",le="+Inf"} 6`, - `kooper_controller_processed_event_duration_seconds_count{controller="test",type="delete"} 6`, - }, - expCode: 200, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - - // Create a new prometheus empty registry and a kooper prometheus recorder. - reg := prometheus.NewRegistry() - m := metrics.NewPrometheus(reg) - - // Add desired metrics - test.addMetrics(m) - - // Ask prometheus for the metrics - h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) - r := httptest.NewRequest("GET", "/metrics", nil) - w := httptest.NewRecorder() - h.ServeHTTP(w, r) - resp := w.Result() - - // Check all metrics are present. - if assert.Equal(test.expCode, resp.StatusCode) { - body, _ := ioutil.ReadAll(resp.Body) - for _, expMetric := range test.expMetrics { - assert.Contains(string(body), expMetric, "metric not present on the result of metrics service") - } - } - }) - } -} diff --git a/operator/controller/config.go b/operator/controller/config.go deleted file mode 100644 index ff08ed95..00000000 --- a/operator/controller/config.go +++ /dev/null @@ -1,36 +0,0 @@ -package controller - -import "time" - -// Defaults. -const ( - defResyncInterval = 30 * time.Second - defConcurrentWorkers = 1 - defProcessingJobRetries = 3 -) - -// Config is the controller configuration. -type Config struct { - // name of the controller. - Name string - // ConcurrentWorkers is the number of concurrent workers the controller will have running processing events. - ConcurrentWorkers int - // ResyncInterval is the interval the controller will process all the selected resources. - ResyncInterval time.Duration - // ProcessingJobRetries is the number of times the job will try to reprocess the event before returning a real error. - ProcessingJobRetries int -} - -func (c *Config) setDefaults() { - if c.ConcurrentWorkers <= 0 { - c.ConcurrentWorkers = defConcurrentWorkers - } - - if c.ResyncInterval <= 0 { - c.ResyncInterval = defResyncInterval - } - - if c.ProcessingJobRetries <= 0 { - c.ProcessingJobRetries = defProcessingJobRetries - } -} diff --git a/operator/controller/controller.go b/operator/controller/controller.go deleted file mode 100644 index df0e5156..00000000 --- a/operator/controller/controller.go +++ /dev/null @@ -1,9 +0,0 @@ -package controller - -// Controller is the object that will implement the different kinds of controllers that will be running -// on the application. -type Controller interface { - // Run runs the controller, it receives a channel that when receiving a signal it will stop the controller, - // Run will block until it's stopped. - Run(stopper <-chan struct{}) error -} diff --git a/operator/controller/generic.go b/operator/controller/generic.go deleted file mode 100644 index e297eeba..00000000 --- a/operator/controller/generic.go +++ /dev/null @@ -1,424 +0,0 @@ -package controller - -import ( - "context" - "fmt" - "reflect" - "sync" - "time" - - opentracing "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/util/workqueue" - - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/monitoring/metrics" - "github.com/spotahome/kooper/operator/controller/leaderelection" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/operator/retrieve" -) - -// Span tag and log keys. -const ( - kubernetesObjectKeyKey = "kubernetes.object.key" - kubernetesObjectNSKey = "kubernetes.object.namespace" - kubernetesObjectNameKey = "kubernetes.object.name" - eventKey = "event" - kooperControllerKey = "kooper.controller" - processedTimesKey = "kubernetes.object.total_processed_times" - retriesRemainingKey = "kubernetes.object.retries_remaining" - processingRetryKey = "kubernetes.object.processing_retry" - retriesExecutedKey = "kubernetes.object.retries_consumed" - controllerNameKey = "controller.cfg.name" - controllerResyncKey = "controller.cfg.resync_interval" - controllerMaxRetriesKey = "controller.cfg.max_retries" - controllerConcurrentWorkersKey = "controller.cfg.concurrent_workers" - successKey = "success" - messageKey = "message" -) - -// generic controller is a controller that can be used to create different kind of controllers. -type generic struct { - queue workqueue.RateLimitingInterface // queue will have the jobs that the controller will get and send to handlers. - informer cache.SharedIndexInformer // informer will notify be inform us about resource changes. - handler handler.Handler // handler is where the logic of resource processing. - running bool - runningMu sync.Mutex - cfg Config - tracer opentracing.Tracer // use directly opentracing API because it's not an implementation. - metrics metrics.Recorder - leRunner leaderelection.Runner - logger log.Logger -} - -// NewSequential creates a new controller that will process the received events sequentially. -// This constructor is just a wrapper to help bootstrapping default sequential controller. -func NewSequential(resync time.Duration, handler handler.Handler, retriever retrieve.Retriever, metricRecorder metrics.Recorder, logger log.Logger) Controller { - cfg := &Config{ - ConcurrentWorkers: 1, - ResyncInterval: resync, - } - return New(cfg, handler, retriever, nil, nil, metricRecorder, logger) -} - -// NewConcurrent creates a new controller that will process the received events concurrently. -// This constructor is just a wrapper to help bootstrapping default concurrent controller. -func NewConcurrent(concurrentWorkers int, resync time.Duration, handler handler.Handler, retriever retrieve.Retriever, metricRecorder metrics.Recorder, logger log.Logger) (Controller, error) { - if concurrentWorkers < 2 { - return nil, fmt.Errorf("%d is not a valid concurrency workers ammount for a concurrent controller", concurrentWorkers) - } - - cfg := &Config{ - ConcurrentWorkers: concurrentWorkers, - ResyncInterval: resync, - } - return New(cfg, handler, retriever, nil, nil, metricRecorder, logger), nil -} - -// New creates a new controller that can be configured using the cfg parameter. -func New(cfg *Config, handler handler.Handler, retriever retrieve.Retriever, leaderElector leaderelection.Runner, tracer opentracing.Tracer, metricRecorder metrics.Recorder, logger log.Logger) Controller { - // Sets the required default configuration. - cfg.setDefaults() - - // Default logger. - if logger == nil { - logger = &log.Std{} - logger.Warningf("no logger specified, fallback to default logger, to disable logging use dummy logger") - } - - // Default metrics recorder. - if metricRecorder == nil { - metricRecorder = metrics.Dummy - logger.Warningf("no metrics recorder specified, disabling metrics") - } - - // Default tracer. - if tracer == nil { - tracer = &opentracing.NoopTracer{} - } - - // If no name on controller do our best to infer a name based on the handler. - if cfg.Name == "" { - cfg.Name = reflect.TypeOf(handler).String() - logger.Warningf("controller name not provided, it should have a name, fallback name to: %s", cfg.Name) - } - - // Create the queue that will have our received job changes. It's rate limited so we don't have problems when - // a job processing errors every time is processed in a loop. - queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) - - // store is the internal cache where objects will be store. - store := cache.Indexers{} - informer := cache.NewSharedIndexInformer(retriever.GetListerWatcher(), retriever.GetObject(), cfg.ResyncInterval, store) - - // Set up our informer event handler. - // Objects are already in our local store. Add only keys/jobs on the queue so they can bre processed - // afterwards. - informer.AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - key, err := cache.MetaNamespaceKeyFunc(obj) - if err == nil { - queue.Add(key) - metricRecorder.IncResourceEventQueued(cfg.Name, metrics.AddEvent) - } - }, - UpdateFunc: func(old interface{}, new interface{}) { - key, err := cache.MetaNamespaceKeyFunc(new) - if err == nil { - queue.Add(key) - metricRecorder.IncResourceEventQueued(cfg.Name, metrics.AddEvent) - } - }, - DeleteFunc: func(obj interface{}) { - key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) - if err == nil { - queue.Add(key) - metricRecorder.IncResourceEventQueued(cfg.Name, metrics.DeleteEvent) - } - }, - }, cfg.ResyncInterval) - - // Create our generic controller object. - return &generic{ - queue: queue, - informer: informer, - logger: logger, - metrics: metricRecorder, - tracer: tracer, - handler: handler, - leRunner: leaderElector, - cfg: *cfg, - } -} - -func (g *generic) isRunning() bool { - g.runningMu.Lock() - defer g.runningMu.Unlock() - return g.running -} - -func (g *generic) setRunning(running bool) { - g.runningMu.Lock() - defer g.runningMu.Unlock() - g.running = running -} - -// Run will run the controller. -func (g *generic) Run(stopC <-chan struct{}) error { - // Check if leader election is required. - if g.leRunner != nil { - return g.leRunner.Run(func() error { - return g.run(stopC) - }) - } - - return g.run(stopC) -} - -// run is the real run of the controller. -func (g *generic) run(stopC <-chan struct{}) error { - if g.isRunning() { - return fmt.Errorf("controller already running") - } - - g.logger.Infof("starting controller") - // Set state of controller. - g.setRunning(true) - defer g.setRunning(false) - - // Shutdown when Run is stopped so we can process the last items and the queue doesn't - // accept more jobs. - defer g.queue.ShutDown() - - // Run the informer so it starts listening to resource events. - go g.informer.Run(stopC) - - // Wait until our store, jobs... stuff is synced (first list on resource, resources on store and jobs on queue). - if !cache.WaitForCacheSync(stopC, g.informer.HasSynced) { - return fmt.Errorf("timed out waiting for caches to sync") - } - - // Start our resource processing worker, if finishes then restart the worker. The workers should - // not end. - for i := 0; i < g.cfg.ConcurrentWorkers; i++ { - go func() { - wait.Until(g.runWorker, time.Second, stopC) - }() - } - - // Until will be running our workers in a continuous way (and re run if they fail). But - // when stop signal is received we must stop. - <-stopC - g.logger.Infof("stopping controller") - - return nil -} - -// runWorker will start a processing loop on event queue. -func (g *generic) runWorker() { - for { - // Process newxt queue job, if needs to stop processing it will return true. - if g.getAndProcessNextJob() { - break - } - } -} - -// getAndProcessNextJob job will process the next job of the queue job and returns if -// it needs to stop processing. -func (g *generic) getAndProcessNextJob() bool { - // Get next job. - nextJob, exit := g.queue.Get() - if exit { - return true - } - defer g.queue.Done(nextJob) - key := nextJob.(string) - - // Our root span will start here. - span := g.tracer.StartSpan("processJob") - defer span.Finish() - ctx := opentracing.ContextWithSpan(context.Background(), span) - g.setRootSpanInfo(key, span) - - // Process the job. If errors then enqueue again. - if err := g.processJob(ctx, key); err == nil { - g.queue.Forget(key) - g.setForgetSpanInfo(key, span, err) - } else if g.queue.NumRequeues(key) < g.cfg.ProcessingJobRetries { - // Job processing failed, requeue. - g.logger.Warningf("error processing %s job (requeued): %v", key, err) - g.queue.AddRateLimited(key) - g.metrics.IncResourceEventQueued(g.cfg.Name, metrics.RequeueEvent) - g.setReenqueueSpanInfo(key, span, err) - } else { - g.logger.Errorf("Error processing %s: %v", key, err) - g.queue.Forget(key) - g.setForgetSpanInfo(key, span, err) - } - - return false -} - -// processJob is where the real processing logic of the item is. -func (g *generic) processJob(ctx context.Context, key string) error { - // Get the object - obj, exists, err := g.informer.GetIndexer().GetByKey(key) - if err != nil { - return err - } - - // handle the object. - if !exists { // Deleted resource from the cache. - return g.handleDelete(ctx, key) - } - - return g.handleAdd(ctx, key, obj.(runtime.Object)) -} - -func (g *generic) handleAdd(ctx context.Context, objKey string, obj runtime.Object) error { - start := time.Now() - g.metrics.IncResourceEventProcessed(g.cfg.Name, metrics.AddEvent) - defer func() { - g.metrics.ObserveDurationResourceEventProcessed(g.cfg.Name, metrics.AddEvent, start) - }() - - // Create the span. - pSpan := opentracing.SpanFromContext(ctx) - span := g.tracer.StartSpan("handleAddObject", opentracing.ChildOf(pSpan.Context())) - ctx = opentracing.ContextWithSpan(ctx, span) - defer span.Finish() - - // Set span data. - ext.SpanKindConsumer.Set(span) - span.SetTag(kubernetesObjectKeyKey, objKey) - g.setCommonSpanInfo(span) - span.LogKV( - eventKey, "add", - kubernetesObjectKeyKey, objKey, - ) - - // Handle the job. - if err := g.handler.Add(ctx, obj); err != nil { - ext.Error.Set(span, true) // Mark error as true. - span.LogKV( - eventKey, "error", - messageKey, err, - ) - - g.metrics.IncResourceEventProcessedError(g.cfg.Name, metrics.AddEvent) - return err - } - return nil -} - -func (g *generic) handleDelete(ctx context.Context, objKey string) error { - start := time.Now() - g.metrics.IncResourceEventProcessed(g.cfg.Name, metrics.DeleteEvent) - defer func() { - g.metrics.ObserveDurationResourceEventProcessed(g.cfg.Name, metrics.DeleteEvent, start) - }() - - // Create the span. - pSpan := opentracing.SpanFromContext(ctx) - span := g.tracer.StartSpan("handleDeleteObject", opentracing.ChildOf(pSpan.Context())) - ctx = opentracing.ContextWithSpan(ctx, span) - defer span.Finish() - - // Set span data. - ext.SpanKindConsumer.Set(span) - span.SetTag(kubernetesObjectKeyKey, objKey) - g.setCommonSpanInfo(span) - span.LogKV( - eventKey, "delete", - kubernetesObjectKeyKey, objKey, - ) - - // Handle the job. - if err := g.handler.Delete(ctx, objKey); err != nil { - ext.Error.Set(span, true) // Mark error as true. - span.LogKV( - eventKey, "error", - messageKey, err, - ) - - g.metrics.IncResourceEventProcessedError(g.cfg.Name, metrics.DeleteEvent) - return err - } - return nil -} - -func (g *generic) setCommonSpanInfo(span opentracing.Span) { - ext.Component.Set(span, "kooper") - span.SetTag(kooperControllerKey, g.cfg.Name) - span.SetTag(controllerNameKey, g.cfg.Name) - span.SetTag(controllerResyncKey, g.cfg.ResyncInterval) - span.SetTag(controllerMaxRetriesKey, g.cfg.ProcessingJobRetries) - span.SetTag(controllerConcurrentWorkersKey, g.cfg.ConcurrentWorkers) -} - -func (g *generic) setRootSpanInfo(key string, span opentracing.Span) { - numberRetries := g.queue.NumRequeues(key) - - // Try to set the namespace and resource name. - if ns, name, err := cache.SplitMetaNamespaceKey(key); err == nil { - span.SetTag(kubernetesObjectNSKey, ns) - span.SetTag(kubernetesObjectNameKey, name) - } - - g.setCommonSpanInfo(span) - span.SetTag(kubernetesObjectKeyKey, key) - span.SetTag(processedTimesKey, numberRetries+1) - span.SetTag(processingRetryKey, numberRetries > 0) - span.SetBaggageItem(kubernetesObjectKeyKey, key) - ext.SpanKindConsumer.Set(span) - span.LogKV( - eventKey, "process_object", - kubernetesObjectKeyKey, key, - ) -} - -func (g *generic) setReenqueueSpanInfo(key string, span opentracing.Span, err error) { - // Mark root span with error. - ext.Error.Set(span, true) - span.LogKV( - eventKey, "error", - messageKey, err, - ) - - rt := g.queue.NumRequeues(key) - span.LogKV( - eventKey, "reenqueued", - retriesRemainingKey, g.cfg.ProcessingJobRetries-rt, - retriesExecutedKey, rt, - kubernetesObjectKeyKey, key, - ) - span.LogKV(successKey, false) -} - -func (g *generic) setForgetSpanInfo(key string, span opentracing.Span, err error) { - success := true - message := "object processed correctly" - - // Error data. - if err != nil { - // Mark root span with error. - ext.Error.Set(span, true) - span.LogKV( - eventKey, "error", - messageKey, err, - ) - success = false - message = "max number of retries reached after failing, forgetting object key" - } - - span.LogKV( - eventKey, "forget", - messageKey, message, - kubernetesObjectKeyKey, key, - ) - span.LogKV(successKey, success) -} diff --git a/operator/controller/generic_test.go b/operator/controller/generic_test.go deleted file mode 100644 index 8115f97f..00000000 --- a/operator/controller/generic_test.go +++ /dev/null @@ -1,509 +0,0 @@ -package controller_test - -import ( - "context" - "fmt" - "testing" - "time" - - opentracing "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/mocktracer" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" - kubetesting "k8s.io/client-go/testing" - "k8s.io/client-go/tools/cache" - - "github.com/spotahome/kooper/log" - mhandler "github.com/spotahome/kooper/mocks/operator/handler" - "github.com/spotahome/kooper/monitoring/metrics" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/controller/leaderelection" -) - -// Namespace knows how to retrieve namespaces. -type namespaceRetriever struct { - lw cache.ListerWatcher - obj runtime.Object -} - -// NewNamespace returns a Namespace retriever. -func newNamespaceRetriever(client kubernetes.Interface) *namespaceRetriever { - return &namespaceRetriever{ - lw: &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return client.CoreV1().Namespaces().List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return client.CoreV1().Namespaces().Watch(options) - }, - }, - obj: &corev1.Namespace{}, - } -} - -// GetListerWatcher knows how to retrieve Namespaces. -func (n *namespaceRetriever) GetListerWatcher() cache.ListerWatcher { - return n.lw -} - -// GetObject returns the namespace Object. -func (n *namespaceRetriever) GetObject() runtime.Object { - return n.obj -} - -func onKubeClientWatchNamespaceReturn(client *fake.Clientset, adds []*corev1.Namespace, updates []*corev1.Namespace, deletes []*corev1.Namespace) { - w := watch.NewFake() - client.AddWatchReactor("namespaces", func(action kubetesting.Action) (bool, watch.Interface, error) { - return true, w, nil - }) - - go func() { - // Adds. - for _, obj := range adds { - w.Add(obj) - } - // Updates. - for _, obj := range updates { - w.Modify(obj) - } - // Deletes. - for _, obj := range deletes { - w.Delete(obj) - } - }() -} - -func onKubeClientListNamespaceReturn(client *fake.Clientset, nss *corev1.NamespaceList) { - client.AddReactor("list", "namespaces", func(action kubetesting.Action) (bool, runtime.Object, error) { - return true, nss, nil - }) -} - -func createNamespaceList(prefix string, q int) (*corev1.NamespaceList, []*corev1.Namespace) { - nss := []*corev1.Namespace{} - nsl := &corev1.NamespaceList{ - ListMeta: metav1.ListMeta{ - ResourceVersion: "1", - }, - Items: []corev1.Namespace{}, - } - - for i := 0; i < q; i++ { - nsName := fmt.Sprintf("%s-%d", prefix, i) - ns := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: nsName, - ResourceVersion: fmt.Sprintf("%d", i), - }, - } - - nsl.Items = append(nsl.Items, ns) - nss = append(nss, &ns) - } - - return nsl, nss -} - -func TestGenericControllerHandleAdds(t *testing.T) { - nsList, expNSAdds := createNamespaceList("testing", 10) - - tests := []struct { - name string - nsList *corev1.NamespaceList - expNSAdds []*corev1.Namespace - }{ - { - name: "Listing multiple namespaces should call as add handlers for every namespace on list.", - nsList: nsList, - expNSAdds: expNSAdds, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - controllerStopperC := make(chan struct{}) - resultC := make(chan error) - - // Mocks kubernetes client. - mc := &fake.Clientset{} - onKubeClientListNamespaceReturn(mc, test.nsList) - - // Mock our handler and set expects. - callHandling := 0 // used to track the number of calls. - mh := &mhandler.Handler{} - for _, ns := range test.expNSAdds { - mh.On("Add", mock.Anything, ns).Once().Return(nil).Run(func(args mock.Arguments) { - callHandling++ - // Check last call, if is the last call expected then stop the controller so - // we can assert the expectations of the calls and finish the test. - if callHandling == len(test.expNSAdds) { - close(controllerStopperC) - } - }) - } - - nsret := newNamespaceRetriever(mc) - c := controller.NewSequential(0, mh, nsret, metrics.Dummy, log.Dummy) - - // Run Controller in background. - go func() { - resultC <- c.Run(controllerStopperC) - }() - - // Wait for different results. If no result means error failure. - select { - case err := <-resultC: - if assert.NoError(err) { - // Check handles from the controller. - mh.AssertExpectations(t) - } - case <-time.After(1 * time.Second): - assert.Fail("timeout waiting for controller handling, this could mean the controller is not receiving resources") - - } - }) - } -} - -func TestGenericControllerHandleDeletes(t *testing.T) { - - startNSList, expNSAdds := createNamespaceList("testing", 10) - nsDels := []*corev1.Namespace{expNSAdds[0], expNSAdds[4], expNSAdds[1]} - - tests := []struct { - name string - startNSList *corev1.NamespaceList - deleteNs []*corev1.Namespace - expDeleteNs []*corev1.Namespace - }{ - { - name: "Deleting multiple namespaces should call as delete handlers for every namespace on deleted.", - startNSList: startNSList, - deleteNs: nsDels, - expDeleteNs: nsDels, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - controllerStopperC := make(chan struct{}) - resultC := make(chan error) - - // Mocks kubernetes client. - mc := &fake.Clientset{} - // Populate cache so we ensure deletes are correctly delivered. - onKubeClientListNamespaceReturn(mc, test.startNSList) - onKubeClientWatchNamespaceReturn(mc, nil, nil, test.deleteNs) - - // Mock our handler and set expects. - callHandling := 0 // used to track the number of calls. - mh := &mhandler.Handler{} - mh.On("Add", mock.Anything, mock.Anything).Return(nil) - for _, ns := range test.expDeleteNs { - mh.On("Delete", mock.Anything, ns.ObjectMeta.Name).Once().Return(nil).Run(func(args mock.Arguments) { - // Check last call, if is the last call expected then stop the controller so - // we can assert the expectations of the calls and finish the test. - callHandling++ - if callHandling == len(test.expDeleteNs) { - close(controllerStopperC) - } - }) - } - - nsret := newNamespaceRetriever(mc) - c := controller.NewSequential(0, mh, nsret, metrics.Dummy, log.Dummy) - - // Run Controller in background. - go func() { - resultC <- c.Run(controllerStopperC) - }() - - // Wait for different results. If no result means error failure. - select { - case err := <-resultC: - if assert.NoError(err) { - // Check handles from the controller. - mh.AssertExpectations(t) - } - case <-time.After(1 * time.Second): - assert.Fail("timeout waiting for controller handling, this could mean the controller is not receiving resources") - } - }) - } -} - -func TestGenericControllerErrorRetries(t *testing.T) { - nsList, _ := createNamespaceList("testing", 11) - - tests := []struct { - name string - nsList *corev1.NamespaceList - retryNumber int - }{ - { - name: "Retrying N resources with M retries and error on all should be 1 + M processing calls per resource (N+N*M event processing calls).", - nsList: nsList, - retryNumber: 3, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - controllerStopperC := make(chan struct{}) - resultC := make(chan error) - - // Mocks kubernetes client. - mc := &fake.Clientset{} - // Populate cache so we ensure deletes are correctly delivered. - onKubeClientListNamespaceReturn(mc, nsList) - - // Mock our handler and set expects. - totalCalls := len(test.nsList.Items) + len(test.nsList.Items)*test.retryNumber - mh := &mhandler.Handler{} - err := fmt.Errorf("wanted error") - - // Expect all the retries - for range test.nsList.Items { - callsPerNS := test.retryNumber + 1 // initial call + retries. - mh.On("Add", mock.Anything, mock.Anything).Return(err).Times(callsPerNS).Run(func(args mock.Arguments) { - totalCalls-- - // Check last call, if is the last call expected then stop the controller so - // we can assert the expectations of the calls and finish the test. - if totalCalls <= 0 { - close(controllerStopperC) - } - }) - } - - nsret := newNamespaceRetriever(mc) - cfg := &controller.Config{ - ProcessingJobRetries: test.retryNumber, - } - c := controller.New(cfg, mh, nsret, nil, nil, metrics.Dummy, log.Dummy) - - // Run Controller in background. - go func() { - resultC <- c.Run(controllerStopperC) - }() - - // Wait for different results. If no result means error failure. - select { - case err := <-resultC: - if assert.NoError(err) { - // Check handles from the controller. - mh.AssertExpectations(t) - } - case <-time.After(1 * time.Second): - assert.Fail("timeout waiting for controller handling, this could mean the controller is not receiving resources") - } - }) - } -} - -func TestGenericControllerWithLeaderElection(t *testing.T) { - nsList, _ := createNamespaceList("testing", 5) - - tests := []struct { - name string - nsList *corev1.NamespaceList - retryNumber int - }{ - { - name: "", - nsList: nsList, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - controllerStopperC := make(chan struct{}) - resultC := make(chan error) - - // Mocks kubernetes client. - mc := fake.NewSimpleClientset(nsList) - - // Mock our handler and set expects. - mh1 := &mhandler.Handler{} - mh2 := &mhandler.Handler{} - mh3 := &mhandler.Handler{} - - // Expect the calls on the lead (mh1) and no calls on the other ones. - totalCalls := len(test.nsList.Items) - mh1.On("Add", mock.Anything, mock.Anything).Return(nil).Times(totalCalls).Run(func(args mock.Arguments) { - totalCalls-- - // Check last call, if is the last call expected then stop the controller so - // we can assert the expectations of the calls and finish the test. - if totalCalls <= 0 { - close(controllerStopperC) - } - }) - - nsret := newNamespaceRetriever(mc) - cfg := &controller.Config{ - ProcessingJobRetries: test.retryNumber, - } - - // Leader election service. - rlCfg := &leaderelection.LockConfig{ - LeaseDuration: 9999 * time.Second, - RenewDeadline: 9998 * time.Second, - RetryPeriod: 500 * time.Second, - } - lesvc1, _ := leaderelection.New("test", "default", rlCfg, mc, log.Dummy) - lesvc2, _ := leaderelection.New("test", "default", rlCfg, mc, log.Dummy) - lesvc3, _ := leaderelection.New("test", "default", rlCfg, mc, log.Dummy) - - c1 := controller.New(cfg, mh1, nsret, lesvc1, nil, metrics.Dummy, log.Dummy) - c2 := controller.New(cfg, mh2, nsret, lesvc2, nil, metrics.Dummy, log.Dummy) - c3 := controller.New(cfg, mh3, nsret, lesvc3, nil, metrics.Dummy, log.Dummy) - - // Run multiple controller in background. - go func() { resultC <- c1.Run(controllerStopperC) }() - // Let the first controller became the leader. - time.Sleep(200 * time.Microsecond) - go func() { resultC <- c2.Run(controllerStopperC) }() - go func() { resultC <- c3.Run(controllerStopperC) }() - - // Wait for different results. If no result means error failure. - select { - case err := <-resultC: - if assert.NoError(err) { - // Check handles from the controller. - mh1.AssertExpectations(t) - mh2.AssertExpectations(t) - mh3.AssertExpectations(t) - } - case <-time.After(1 * time.Second): - assert.Fail("timeout waiting for controller handling, this could mean the controller is not receiving resources") - } - }) - } -} - -func TestGenericControllerTracing(t *testing.T) { - tests := []struct { - name string - addNs corev1.Namespace - addErr error - maxRetries int - expNS string - expTraces int - }{ - { - name: "Listing a namespace should only create a trace.", - addNs: corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-example", - }, - }, - expNS: "test-example", - expTraces: 2, - }, - { - name: "Listing a namespace with a handling error should mark the traces with error.", - addNs: corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-example2", - }, - }, - addErr: fmt.Errorf("wanted error"), - maxRetries: 1, - expNS: "test-example2", - expTraces: 2, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - controllerStopperC := make(chan struct{}) - done := make(chan struct{}) - - // Mocks kubernetes client. - mc := &fake.Clientset{} - onKubeClientListNamespaceReturn(mc, &corev1.NamespaceList{ - ListMeta: metav1.ListMeta{ - ResourceVersion: "1", - }, - Items: []corev1.Namespace{test.addNs}}) - - // Mock our handler. - mh := &mhandler.Handler{} - mh.On("Add", mock.Anything, mock.Anything).Return(test.addErr).Run(func(args mock.Arguments) { - // Check the received context has a span. - ctx := args.Get(0).(context.Context) - span := opentracing.SpanFromContext(ctx) - assert.NotNil(span) - - // Done. Send signal so we can check spans. - close(done) - }) - - tracer := mocktracer.New() - nsret := newNamespaceRetriever(mc) - cfg := &controller.Config{ - Name: "test-tracing", - ProcessingJobRetries: test.maxRetries, - } - - c := controller.New(cfg, mh, nsret, nil, tracer, nil, log.Dummy) - - // Run Controller in background. - go func() { - c.Run(controllerStopperC) - }() - - // Wait for different results. If no result means error failure. - select { - case <-done: - // Wait until the parent spans are finished. - time.Sleep(2 * time.Millisecond) - - // Check we have the correct number of finished spans. - finishedSpans := tracer.FinishedSpans() - // Process object and add/delete spans. - if assert.Len(finishedSpans, test.expTraces) { - // Get the spans and check correlation, is in finished order, so it should be reversed. - rootSpan := finishedSpans[1] - currentSpan := finishedSpans[0] - rootSpanCtx := rootSpan.Context().(mocktracer.MockSpanContext) - assert.Equal(rootSpanCtx.SpanID, currentSpan.ParentID) - - // Check span operation names. - assert.Equal("processJob", rootSpan.OperationName) - assert.Equal("handleAddObject", currentSpan.OperationName) - - // Check important tags. - if assert.Contains(rootSpan.Tags(), "kubernetes.object.key") { - assert.Equal(test.expNS, rootSpan.Tags()["kubernetes.object.key"]) - } - if assert.Contains(currentSpan.Tags(), "kubernetes.object.key") { - assert.Equal(test.expNS, currentSpan.Tags()["kubernetes.object.key"]) - } - - // Check if error. - if test.addErr != nil { - if assert.Contains(rootSpan.Tags(), "error") { - assert.Equal(true, rootSpan.Tags()["error"]) - } - if assert.Contains(currentSpan.Tags(), "error") { - assert.Equal(true, currentSpan.Tags()["error"]) - } - } - } - case <-time.After(1 * time.Second): - assert.Fail("timeout waiting for controller handling, this could mean the controller is not receiving resources") - } - }) - } -} diff --git a/operator/doc.go b/operator/doc.go deleted file mode 100644 index 057f70ea..00000000 --- a/operator/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package operator contains implementations and definitions to create Kubernetes operators -// and controllers -package operator // import "github.com/spotahome/kooper/operator" diff --git a/operator/handler/common.go b/operator/handler/common.go deleted file mode 100644 index e175ee94..00000000 --- a/operator/handler/common.go +++ /dev/null @@ -1,34 +0,0 @@ -package handler - -import ( - "context" - - "k8s.io/apimachinery/pkg/runtime" - - "github.com/spotahome/kooper/log" -) - -// Logger will log the handling events. This handler can be sued to test that a -// controller receives resource events.. -type Logger struct { - logger log.Logger -} - -// NewLogger returns a new logger. -func NewLogger(logger log.Logger) *Logger { - return &Logger{ - logger: logger, - } -} - -// Add satisfies Handler interface. -func (l *Logger) Add(_ context.Context, obj runtime.Object) error { - l.logger.Infof("event add: %#v", obj) - return nil -} - -// Delete satisfies Handler interface. -func (l *Logger) Delete(_ context.Context, objKey string) error { - l.logger.Infof("event delete: %#v", objKey) - return nil -} diff --git a/operator/handler/handler.go b/operator/handler/handler.go deleted file mode 100644 index 508cc818..00000000 --- a/operator/handler/handler.go +++ /dev/null @@ -1,43 +0,0 @@ -package handler - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/runtime" -) - -// Handler knows how to handle the received resources from a kubernetes cluster. -type Handler interface { - Add(context.Context, runtime.Object) error - Delete(context.Context, string) error -} - -// AddFunc knows how to handle resource adds. -type AddFunc func(context.Context, runtime.Object) error - -// DeleteFunc knows how to handle resource deletes. -type DeleteFunc func(context.Context, string) error - -// HandlerFunc is a handler that is created from functions that the -// Handler interface requires. -type HandlerFunc struct { - AddFunc AddFunc - DeleteFunc DeleteFunc -} - -// Add satisfies Handler interface. -func (h *HandlerFunc) Add(ctx context.Context, obj runtime.Object) error { - if h.AddFunc == nil { - return fmt.Errorf("function can't be nil") - } - return h.AddFunc(ctx, obj) -} - -// Delete satisfies Handler interface. -func (h *HandlerFunc) Delete(ctx context.Context, s string) error { - if h.DeleteFunc == nil { - return fmt.Errorf("function can't be nil") - } - return h.DeleteFunc(ctx, s) -} diff --git a/operator/operator.go b/operator/operator.go deleted file mode 100644 index 8cfa74fa..00000000 --- a/operator/operator.go +++ /dev/null @@ -1,172 +0,0 @@ -package operator - -import ( - "fmt" - "sync" - "time" - - "golang.org/x/sync/errgroup" - - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/resource" -) - -const ( - tiemoutInitialization = 1 * time.Minute -) - -// Operator is a controller, at code level have almost same contract of behaviour -// but at a higher level it need to initialize some resources (usually CRDs) before -// start its execution. -type Operator interface { - // Initialize knows how to initialize the resources. - Initialize() error - controller.Controller -} - -// simpleOperator is an operator that initializes CRDs before starting -// the execution of controllers. -type simpleOperator struct { - crds []resource.CRD - controllers []controller.Controller - initialized bool - running bool - stateMu sync.Mutex - logger log.Logger -} - -// NewOperator will return an operator that only manages one CRD -// and one Controller. -func NewOperator(crd resource.CRD, ctrlr controller.Controller, logger log.Logger) Operator { - return NewMultiOperator([]resource.CRD{crd}, []controller.Controller{ctrlr}, logger) -} - -// NewMultiOperator returns an operator that has multiple CRDs and controllers. -func NewMultiOperator(crds []resource.CRD, ctrlrs []controller.Controller, logger log.Logger) Operator { - return &simpleOperator{ - crds: crds, - controllers: ctrlrs, - logger: logger, - } -} - -// Initialize will initializer all the CRDs and return. Satisfies Operator interface. -func (s *simpleOperator) Initialize() error { - if s.isInitialized() { - return nil - } - - // Initialize CRDs. - var g errgroup.Group - for _, crd := range s.crds { - crd := crd - g.Go(func() error { - return crd.Initialize() - }) - } - - // Wait until everything is initialized. - errC := make(chan error) - go func() { - errC <- g.Wait() - }() - - select { - case err := <-errC: - if err != nil { - return err - } - case <-time.After(tiemoutInitialization): - return fmt.Errorf("timeout initializing operator") - } - - // All ok, we are ready to run. - s.logger.Infof("operator initialized") - s.setInitialized(true) - return nil -} - -// Run will run the operator (a.k.a) the controllers and Initialize the CRDs. -// It's a blocking operation. Satisfies Operator interface. The client that uses an operator -// has the responsibility of closing the stop channel if the operator ends execution -// unexpectly so all the goroutines (controllers running) end its execution -func (s *simpleOperator) Run(stopC <-chan struct{}) error { - if s.isRunning() { - return fmt.Errorf("operator already running") - } - - s.logger.Infof("starting operator") - s.setRunning(true) - defer s.setRunning(false) - - if err := s.Initialize(); err != nil { - return err - } - - errC := make(chan error) - go func() { - errC <- s.runAllControllers(stopC) - }() - - // When stop signal is received we must stop or when an error is received from - // one of the controllers. - select { - case err := <-errC: - if err != nil { - return err - } - case <-stopC: - } - s.logger.Infof("stopping operator") - return nil -} - -// runAllControllers will run controllers and block execution. -func (s *simpleOperator) runAllControllers(stopC <-chan struct{}) error { - errC := make(chan error) - - for _, ctrl := range s.controllers { - ctrl := ctrl - go func() { - errC <- ctrl.Run(stopC) - }() - } - - // Wait until any of the controllers end execution. All the controllers should be executing so - // if we receive any result (error or not) we should return an error - select { - case <-stopC: - return nil - case err := <-errC: - if err != nil { - return fmt.Errorf("a controller ended with an error: %s", err) - } - s.logger.Warningf("a controller stopped execution without error before operator received stop signal, stopping operator.") - return nil - } -} - -func (s *simpleOperator) isInitialized() bool { - s.stateMu.Lock() - defer s.stateMu.Unlock() - return s.initialized -} - -func (s *simpleOperator) setInitialized(value bool) { - s.stateMu.Lock() - defer s.stateMu.Unlock() - s.initialized = value -} - -func (s *simpleOperator) isRunning() bool { - s.stateMu.Lock() - defer s.stateMu.Unlock() - return s.running -} - -func (s *simpleOperator) setRunning(value bool) { - s.stateMu.Lock() - defer s.stateMu.Unlock() - s.running = value -} diff --git a/operator/operator_test.go b/operator/operator_test.go deleted file mode 100644 index 05ff2ab5..00000000 --- a/operator/operator_test.go +++ /dev/null @@ -1,220 +0,0 @@ -package operator_test - -import ( - "errors" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/spotahome/kooper/log" - mcontroller "github.com/spotahome/kooper/mocks/operator/controller" - mresource "github.com/spotahome/kooper/mocks/operator/resource" - "github.com/spotahome/kooper/operator" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/resource" -) - -func TestMultiOperatorInitialization(t *testing.T) { - tests := []struct { - name string - errInit bool - expErr bool - }{ - { - name: "Calling multiple initializations should only ininitialize the once.", - errInit: false, - expErr: false, - }, - { - name: "Error on initialization should return error.", - errInit: true, - expErr: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - qCRD := 5 - - var errInit error - if test.errInit { - errInit = errors.New("wanted error") - } - - // Mocks. - mcrds := []resource.CRD{} - for i := 0; i < qCRD; i++ { - crd := &mresource.CRD{} - crd.On("Initialize").Once().Return(errInit) - mcrds = append(mcrds, crd) - } - - // Operator. - op := operator.NewMultiOperator(mcrds, nil, log.Dummy) - err := op.Initialize() - - if test.expErr { - assert.Error(err) - } else { - assert.NoError(err) - assert.NoError(op.Initialize(), "calling multiple times after initialization should not error") - } - for _, crd := range mcrds { - mcrd := crd.(*mresource.CRD) - mcrd.AssertExpectations(t) - } - }) - } -} - -// controllerBehaviour is the way of controlling the beehaviour expected of a controller. -type controllerBehaviour struct { - returnErr bool - returnAfter time.Duration -} - -func createControllerMocks(cb []*controllerBehaviour) []controller.Controller { - mctrls := make([]controller.Controller, len(cb)) - - for i, b := range cb { - // If we need to return an error return an error. - var ctrlErr error - if b.returnErr { - ctrlErr = errors.New("wanted error") - } - - ctrl := &mcontroller.Controller{} - c := ctrl.On("Run", mock.Anything).Once().Return(ctrlErr) - - // If we need to wait anytime then delay the return. - if b.returnAfter > 0 { - c.After(b.returnAfter) - } - - mctrls[i] = ctrl - } - return mctrls -} - -func TestMultiOperatorRun(t *testing.T) { - tests := []struct { - name string - controllers []*controllerBehaviour - expErr bool - expEndOfOperator bool - }{ - { - name: "Running the operator should run without error if the controllers dont end.", - controllers: []*controllerBehaviour{ - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - }, - expErr: false, - expEndOfOperator: false, - }, - { - name: "Running the operator should end without error if one of the controllers ends.", - controllers: []*controllerBehaviour{ - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - { - returnErr: false, - returnAfter: 0, // The one that ends. - }, - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - }, - expErr: false, - expEndOfOperator: true, - }, - { - name: "Running the operator should end with error if one of the controllers ends with an error.", - controllers: []*controllerBehaviour{ - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - { - returnErr: true, - returnAfter: 0, // The one that ends. - }, - { - returnErr: false, - returnAfter: 9999 * time.Hour, - }, - }, - expErr: true, - expEndOfOperator: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - operatorStopperC := make(chan struct{}) - - // Mocks. - crd := &mresource.CRD{} - crd.On("Initialize").Return(nil) - mcrds := []resource.CRD{crd} - - mctrls := createControllerMocks(test.controllers) - - // Operator. - op := operator.NewMultiOperator(mcrds, mctrls, log.Dummy) - - // Run in background and wait to the signals so it can test. - errC := make(chan error) - go func() { - errC <- op.Run(operatorStopperC) - }() - - // If the operator isn't expected to end we should end on our side. - if !test.expEndOfOperator { - // Wait a bit so the calls to controllers are done and then stop operator. - time.Sleep(10 * time.Millisecond) - close(operatorStopperC) - } - select { - case err := <-errC: - // Check. - if test.expErr { - assert.Error(err) - } else { - assert.NoError(err) - } - case <-time.After(1 * time.Second): - assert.Fail("timeout waiting for controllers execution") - } - }) - } -} diff --git a/operator/resource/crd.go b/operator/resource/crd.go deleted file mode 100644 index 0fa4f682..00000000 --- a/operator/resource/crd.go +++ /dev/null @@ -1,12 +0,0 @@ -package resource - -import ( - "github.com/spotahome/kooper/operator/retrieve" -) - -// CRD represents a non stadandard resource or custom resource definition. -type CRD interface { - retrieve.Retriever - // Initialize knows how to ensure that the CRD is initialized. - Initialize() error -} diff --git a/operator/retrieve/retrieve.go b/operator/retrieve/retrieve.go deleted file mode 100644 index 45e5ab2e..00000000 --- a/operator/retrieve/retrieve.go +++ /dev/null @@ -1,30 +0,0 @@ -package retrieve - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/tools/cache" -) - -// Retriever is a way of wrapping kubernetes lister watchers so they are easy to pass & manage them -// on Controllers. -type Retriever interface { - GetListerWatcher() cache.ListerWatcher - GetObject() runtime.Object -} - -// Resource is a helper so you can don't need to create a new type of the -// Retriever interface. -type Resource struct { - ListerWatcher cache.ListerWatcher - Object runtime.Object -} - -// GetListerWatcher satisfies retriever interface. -func (r *Resource) GetListerWatcher() cache.ListerWatcher { - return r.ListerWatcher -} - -// GetObject satisfies retriever interface -func (r *Resource) GetObject() runtime.Object { - return r.Object -} diff --git a/test/integration/controller/controller_test.go b/test/integration/controller/controller_test.go index accc86fe..b1dbc699 100644 --- a/test/integration/controller/controller_test.go +++ b/test/integration/controller/controller_test.go @@ -4,7 +4,6 @@ package controller_test import ( "context" - "strings" "sync" "testing" "time" @@ -17,24 +16,20 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/cache" - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/operator/retrieve" - "github.com/spotahome/kooper/test/integration/helper/cli" - "github.com/spotahome/kooper/test/integration/helper/prepare" + "github.com/spotahome/kooper/v2/controller" + "github.com/spotahome/kooper/v2/log" + "github.com/spotahome/kooper/v2/test/integration/helper/cli" + "github.com/spotahome/kooper/v2/test/integration/helper/prepare" ) // TestControllerHandleEvents will test the controller receives the resources list and watch // events are received and handled correctly. func TestControllerHandleEvents(t *testing.T) { tests := []struct { - name string - addServices []*corev1.Service - updateServices []string - delServices []string - expAddedServices []string - expDeletedServices []string + name string + addServices []*corev1.Service + updateServices []string + expAddedServices []string }{ { name: "If a controller is watching services it should react to the service change events.", @@ -58,10 +53,8 @@ func TestControllerHandleEvents(t *testing.T) { }, }, }, - updateServices: []string{"svc1"}, - delServices: []string{"svc1", "svc2"}, - expAddedServices: []string{"svc1", "svc2", "svc1"}, - expDeletedServices: []string{"svc1", "svc2"}, + updateServices: []string{"svc1"}, + expAddedServices: []string{"svc1", "svc2", "svc1"}, }, } @@ -72,10 +65,9 @@ func TestControllerHandleEvents(t *testing.T) { resync := 30 * time.Second stopC := make(chan struct{}) var gotAddedServices []string - var gotDeletedServices []string // Create the kubernetes client. - k8scli, _, _, err := cli.GetK8sClients("") + k8scli, err := cli.GetK8sClient("") require.NoError(err, "kubernetes client is required") @@ -84,49 +76,38 @@ func TestControllerHandleEvents(t *testing.T) { prep.SetUp() defer prep.TearDown() - // Create the reitrever. - rt := &retrieve.Resource{ - ListerWatcher: cache.NewListWatchFromClient(k8scli.CoreV1().RESTClient(), "services", prep.Namespace().Name, fields.Everything()), - Object: &corev1.Service{}, - } + // Create the retriever. + rt := controller.MustRetrieverFromListerWatcher(cache.NewListWatchFromClient(k8scli.CoreV1().RESTClient(), "services", prep.Namespace().Name, fields.Everything())) // Call times are the number of times the handler should be called before sending the termination signal. - stopCallTimes := len(test.addServices) + len(test.updateServices) + len(test.delServices) + stopCallTimes := len(test.addServices) + len(test.updateServices) calledTimes := 0 var mx sync.Mutex // Create the handler. - hl := &handler.HandlerFunc{ - AddFunc: func(_ context.Context, obj runtime.Object) error { - mx.Lock() - calledTimes++ - mx.Unlock() - - svc := obj.(*corev1.Service) - gotAddedServices = append(gotAddedServices, svc.Name) - if calledTimes >= stopCallTimes { - close(stopC) - } - return nil - }, - DeleteFunc: func(_ context.Context, id string) error { - mx.Lock() - calledTimes++ - mx.Unlock() - - // Ignore namespace. - id = strings.Split(id, "/")[1] - gotDeletedServices = append(gotDeletedServices, id) - if calledTimes >= stopCallTimes { - close(stopC) - } - return nil - }, - } + hl := controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error { + mx.Lock() + calledTimes++ + mx.Unlock() + + svc := obj.(*corev1.Service) + gotAddedServices = append(gotAddedServices, svc.Name) + if calledTimes >= stopCallTimes { + close(stopC) + } + return nil + }) // Create a Pod controller. - ctrl := controller.NewSequential(resync, hl, rt, nil, log.Dummy) - require.NotNil(ctrl, "controller is required") + cfg := &controller.Config{ + Name: "test-controller", + Handler: hl, + Retriever: rt, + Logger: log.Dummy, + ResyncInterval: resync, + } + ctrl, err := controller.New(cfg) + require.NoError(err, "controller is required, can't have error on creation") go ctrl.Run(stopC) // Create the required services. @@ -147,13 +128,6 @@ func TestControllerHandleEvents(t *testing.T) { } } - // Delete the required services. - for _, svc := range test.delServices { - err := k8scli.CoreV1().Services(prep.Namespace().Name).Delete(svc, &metav1.DeleteOptions{}) - assert.NoError(err) - time.Sleep(1 * time.Second) - } - // Wait until we have finished. select { // Timeout. @@ -164,7 +138,6 @@ func TestControllerHandleEvents(t *testing.T) { // Check. assert.Equal(test.expAddedServices, gotAddedServices) - assert.Equal(test.expDeletedServices, gotDeletedServices) }) } } diff --git a/test/integration/controller/generic_test.go b/test/integration/controller/generic_test.go index af715fe4..b8072a39 100644 --- a/test/integration/controller/generic_test.go +++ b/test/integration/controller/generic_test.go @@ -16,11 +16,8 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/tools/cache" - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/monitoring/metrics" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/operator/retrieve" + "github.com/spotahome/kooper/v2/controller" + "github.com/spotahome/kooper/v2/log" ) const ( @@ -53,45 +50,39 @@ func runTimedController(sleepDuration time.Duration, concurrencyLevel int, numbe // Create the faked retriever that will only return N pods. podList := returnPodList(numberOfEvents) - r := &retrieve.Resource{ - Object: &corev1.Pod{}, - ListerWatcher: &cache.ListWatch{ - ListFunc: func(_ metav1.ListOptions) (runtime.Object, error) { - return podList, nil - }, - WatchFunc: func(_ metav1.ListOptions) (watch.Interface, error) { - return watch.NewFake(), nil - }, + r := controller.MustRetrieverFromListerWatcher(&cache.ListWatch{ + ListFunc: func(_ metav1.ListOptions) (runtime.Object, error) { + return podList, nil }, - } + WatchFunc: func(_ metav1.ListOptions) (watch.Interface, error) { + return watch.NewFake(), nil + }, + }) // Create the handler that will wait on each event T duration and will // end when all the wanted quantity of events have been processed. var wg sync.WaitGroup wg.Add(numberOfEvents) - h := &handler.HandlerFunc{ - AddFunc: func(_ context.Context, _ runtime.Object) error { - time.Sleep(sleepDuration) - wg.Done() - return nil - }, - DeleteFunc: func(_ context.Context, _ string) error { - assert.Fail("delete events should not be used on this test") - return nil - }, - } + h := controller.HandlerFunc(func(_ context.Context, _ runtime.Object) error { + time.Sleep(sleepDuration) + wg.Done() + return nil + }) // Create the controller type depending on the concurrency level. - var ctrl controller.Controller - var err error - if concurrencyLevel < 2 { - ctrl = controller.NewSequential(noResync, h, r, metrics.Dummy, log.Dummy) - } else { - ctrl, err = controller.NewConcurrent(concurrencyLevel, noResync, h, r, metrics.Dummy, log.Dummy) - if !assert.NoError(err) { - return 0 - } + cfg := &controller.Config{ + Name: "test-controller", + Handler: h, + Retriever: r, + Logger: log.Dummy, + ProcessingJobRetries: concurrencyLevel, + ResyncInterval: noResync, + ConcurrentWorkers: concurrencyLevel, + } + ctrl, err := controller.New(cfg) + if !assert.NoError(err) { + return 0 } // Run handling diff --git a/test/integration/helper/cli/cli.go b/test/integration/helper/cli/cli.go index a0d8a9f3..904350fb 100644 --- a/test/integration/helper/cli/cli.go +++ b/test/integration/helper/cli/cli.go @@ -5,19 +5,14 @@ import ( "os" "path/filepath" - integrationtestk8scli "github.com/spotahome/kooper/test/integration/operator/client/k8s/clientset/versioned" - apiextensionscli "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" // Load oidc authentication when creating the kubernetes client. "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) -// GetK8sClients returns a all k8s clients. -// * Kubernetes core resources client. -// * Kubernetes api extensions client. -// * Custom test integration CR client. -func GetK8sClients(kubehome string) (kubernetes.Interface, apiextensionscli.Interface, integrationtestk8scli.Interface, error) { +// GetK8sClient returns k8s client. +func GetK8sClient(kubehome string) (kubernetes.Interface, error) { // Try fallbacks. if kubehome == "" { if kubehome = os.Getenv("KUBECONFIG"); kubehome == "" { @@ -28,26 +23,14 @@ func GetK8sClients(kubehome string) (kubernetes.Interface, apiextensionscli.Inte // Load kubernetes local connection. config, err := clientcmd.BuildConfigFromFlags("", kubehome) if err != nil { - return nil, nil, nil, fmt.Errorf("could not load configuration: %s", err) + return nil, fmt.Errorf("could not load configuration: %s", err) } // Get the client. k8sCli, err := kubernetes.NewForConfig(config) if err != nil { - return nil, nil, nil, err + return nil, err } - // App CRD k8s types client. - itCli, err := integrationtestk8scli.NewForConfig(config) - if err != nil { - return nil, nil, nil, err - } - - // api extensions cli. - aexCli, err := apiextensionscli.NewForConfig(config) - if err != nil { - return nil, nil, nil, err - } - - return k8sCli, aexCli, itCli, nil + return k8sCli, nil } diff --git a/test/integration/helper/prepare/prepare.go b/test/integration/helper/prepare/prepare.go index 9a0e012e..8c337058 100644 --- a/test/integration/helper/prepare/prepare.go +++ b/test/integration/helper/prepare/prepare.go @@ -1,10 +1,10 @@ package prepare import ( - "strings" + "fmt" "testing" + "time" - randomdata "github.com/Pallinder/go-randomdata" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -22,7 +22,7 @@ type Preparer struct { func New(cli kubernetes.Interface, t *testing.T) *Preparer { ns := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ - Name: strings.ToLower(randomdata.SillyName()), + Name: fmt.Sprintf("kooper-integration-test-%d", time.Now().UTC().UnixNano()), }, } return &Preparer{ diff --git a/test/integration/operator/Makefile b/test/integration/operator/Makefile deleted file mode 100644 index 74cf4dab..00000000 --- a/test/integration/operator/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -DIRECTORY := $(PWD) -CODE_GENERATOR_IMAGE=quay.io/slok/kube-code-generator:v1.13.5 -CODE_GENERATOR_PACKAGE=github.com/spotahome/kooper/test/integration/operator -GROUPS_VERSION="superhero:v1alpha1" - -default: generate - -.PHONY: generate -generate: - docker run --rm -it \ - -v $(DIRECTORY):/go/src/$(CODE_GENERATOR_PACKAGE) \ - -e PROJECT_PACKAGE=$(CODE_GENERATOR_PACKAGE) \ - -e CLIENT_GENERATOR_OUT=$(CODE_GENERATOR_PACKAGE)/client/k8s \ - -e APIS_ROOT=$(CODE_GENERATOR_PACKAGE)/apis \ - -e GROUPS_VERSION=$(GROUPS_VERSION) \ - -e GENERATION_TARGETS="deepcopy,client" \ - $(CODE_GENERATOR_IMAGE) \ No newline at end of file diff --git a/test/integration/operator/apis/superhero/doc.go b/test/integration/operator/apis/superhero/doc.go deleted file mode 100644 index 3ba092ce..00000000 --- a/test/integration/operator/apis/superhero/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -package superhero - -const ( - GroupName = "superhero.comic.kooper" -) diff --git a/test/integration/operator/apis/superhero/v1alpha1/doc.go b/test/integration/operator/apis/superhero/v1alpha1/doc.go deleted file mode 100644 index f94213dc..00000000 --- a/test/integration/operator/apis/superhero/v1alpha1/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// +k8s:deepcopy-gen=package,register -// +k8s:openapi-gen=true - -// Package v1alpha1 is the v1alpha1 version of the API. -// +groupName=superhero.comic.kooper -package v1alpha1 diff --git a/test/integration/operator/apis/superhero/v1alpha1/register.go b/test/integration/operator/apis/superhero/v1alpha1/register.go deleted file mode 100644 index dc5a4504..00000000 --- a/test/integration/operator/apis/superhero/v1alpha1/register.go +++ /dev/null @@ -1,49 +0,0 @@ -package v1alpha1 - -import ( - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/spotahome/kooper/test/integration/operator/apis/superhero" -) - -const ( - version = "v1alpha1" -) - -// Spiderman constants -const ( - SpidermanKind = "Spiderman" - SpidermanName = "spiderman" - SpidermanNamePlural = "spidermans" - SpidermanNameMin = "spd" - SpidermanScope = apiextensionsv1beta1.NamespaceScoped -) - -// SpidermanShortName is used to register resource short names -var SpidermanShortNames = []string{"spd", "spm"} - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: superhero.GroupName, Version: version} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - AddToScheme = SchemeBuilder.AddToScheme -) - -// Adds the list of known types to Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Spiderman{}, - &SpidermanList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/test/integration/operator/apis/superhero/v1alpha1/types.go b/test/integration/operator/apis/superhero/v1alpha1/types.go deleted file mode 100644 index 58a7af2f..00000000 --- a/test/integration/operator/apis/superhero/v1alpha1/types.go +++ /dev/null @@ -1,37 +0,0 @@ -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:openapi-gen=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +resource:path=spidermans - -// Spiderman is a superhero -type Spiderman struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec is the Spiderman spec. - Spec SpidermanSpec `json:"spec,omitempty"` -} - -// SpidermanSpec contains the specification for Spiderman. -type SpidermanSpec struct { - Name string `json:"name"` - Year int `json:"year,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// SpidermanList is a collection of spidermans. -type SpidermanList struct { - metav1.TypeMeta `json:",inline"` - // +optional - metav1.ListMeta `json:"metadata,omitempty"` - Items []Spiderman `json:"items"` -} diff --git a/test/integration/operator/apis/superhero/v1alpha1/zz_generated.deepcopy.go b/test/integration/operator/apis/superhero/v1alpha1/zz_generated.deepcopy.go deleted file mode 100644 index 34dff4b0..00000000 --- a/test/integration/operator/apis/superhero/v1alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,101 +0,0 @@ -// +build !ignore_autogenerated - -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Spiderman) DeepCopyInto(out *Spiderman) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Spiderman. -func (in *Spiderman) DeepCopy() *Spiderman { - if in == nil { - return nil - } - out := new(Spiderman) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Spiderman) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SpidermanList) DeepCopyInto(out *SpidermanList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Spiderman, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpidermanList. -func (in *SpidermanList) DeepCopy() *SpidermanList { - if in == nil { - return nil - } - out := new(SpidermanList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *SpidermanList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SpidermanSpec) DeepCopyInto(out *SpidermanSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpidermanSpec. -func (in *SpidermanSpec) DeepCopy() *SpidermanSpec { - if in == nil { - return nil - } - out := new(SpidermanSpec) - in.DeepCopyInto(out) - return out -} diff --git a/test/integration/operator/client/k8s/clientset/versioned/clientset.go b/test/integration/operator/client/k8s/clientset/versioned/clientset.go deleted file mode 100644 index e03f7e38..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/clientset.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package versioned - -import ( - superherov1alpha1 "github.com/spotahome/kooper/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1" - discovery "k8s.io/client-go/discovery" - rest "k8s.io/client-go/rest" - flowcontrol "k8s.io/client-go/util/flowcontrol" -) - -type Interface interface { - Discovery() discovery.DiscoveryInterface - SuperheroV1alpha1() superherov1alpha1.SuperheroV1alpha1Interface - // Deprecated: please explicitly pick a version if possible. - Superhero() superherov1alpha1.SuperheroV1alpha1Interface -} - -// Clientset contains the clients for groups. Each group has exactly one -// version included in a Clientset. -type Clientset struct { - *discovery.DiscoveryClient - superheroV1alpha1 *superherov1alpha1.SuperheroV1alpha1Client -} - -// SuperheroV1alpha1 retrieves the SuperheroV1alpha1Client -func (c *Clientset) SuperheroV1alpha1() superherov1alpha1.SuperheroV1alpha1Interface { - return c.superheroV1alpha1 -} - -// Deprecated: Superhero retrieves the default version of SuperheroClient. -// Please explicitly pick a version. -func (c *Clientset) Superhero() superherov1alpha1.SuperheroV1alpha1Interface { - return c.superheroV1alpha1 -} - -// Discovery retrieves the DiscoveryClient -func (c *Clientset) Discovery() discovery.DiscoveryInterface { - if c == nil { - return nil - } - return c.DiscoveryClient -} - -// NewForConfig creates a new Clientset for the given config. -func NewForConfig(c *rest.Config) (*Clientset, error) { - configShallowCopy := *c - if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { - configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) - } - var cs Clientset - var err error - cs.superheroV1alpha1, err = superherov1alpha1.NewForConfig(&configShallowCopy) - if err != nil { - return nil, err - } - - cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) - if err != nil { - return nil, err - } - return &cs, nil -} - -// NewForConfigOrDie creates a new Clientset for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *Clientset { - var cs Clientset - cs.superheroV1alpha1 = superherov1alpha1.NewForConfigOrDie(c) - - cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) - return &cs -} - -// New creates a new Clientset for the given RESTClient. -func New(c rest.Interface) *Clientset { - var cs Clientset - cs.superheroV1alpha1 = superherov1alpha1.New(c) - - cs.DiscoveryClient = discovery.NewDiscoveryClient(c) - return &cs -} diff --git a/test/integration/operator/client/k8s/clientset/versioned/doc.go b/test/integration/operator/client/k8s/clientset/versioned/doc.go deleted file mode 100644 index 41721ca5..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated clientset. -package versioned diff --git a/test/integration/operator/client/k8s/clientset/versioned/fake/clientset_generated.go b/test/integration/operator/client/k8s/clientset/versioned/fake/clientset_generated.go deleted file mode 100644 index e3132511..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/fake/clientset_generated.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - clientset "github.com/spotahome/kooper/test/integration/operator/client/k8s/clientset/versioned" - superherov1alpha1 "github.com/spotahome/kooper/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1" - fakesuperherov1alpha1 "github.com/spotahome/kooper/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/discovery" - fakediscovery "k8s.io/client-go/discovery/fake" - "k8s.io/client-go/testing" -) - -// NewSimpleClientset returns a clientset that will respond with the provided objects. -// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, -// without applying any validations and/or defaults. It shouldn't be considered a replacement -// for a real clientset and is mostly useful in simple unit tests. -func NewSimpleClientset(objects ...runtime.Object) *Clientset { - o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) - for _, obj := range objects { - if err := o.Add(obj); err != nil { - panic(err) - } - } - - cs := &Clientset{} - cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} - cs.AddReactor("*", "*", testing.ObjectReaction(o)) - cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { - gvr := action.GetResource() - ns := action.GetNamespace() - watch, err := o.Watch(gvr, ns) - if err != nil { - return false, nil, err - } - return true, watch, nil - }) - - return cs -} - -// Clientset implements clientset.Interface. Meant to be embedded into a -// struct to get a default implementation. This makes faking out just the method -// you want to test easier. -type Clientset struct { - testing.Fake - discovery *fakediscovery.FakeDiscovery -} - -func (c *Clientset) Discovery() discovery.DiscoveryInterface { - return c.discovery -} - -var _ clientset.Interface = &Clientset{} - -// SuperheroV1alpha1 retrieves the SuperheroV1alpha1Client -func (c *Clientset) SuperheroV1alpha1() superherov1alpha1.SuperheroV1alpha1Interface { - return &fakesuperherov1alpha1.FakeSuperheroV1alpha1{Fake: &c.Fake} -} - -// Superhero retrieves the SuperheroV1alpha1Client -func (c *Clientset) Superhero() superherov1alpha1.SuperheroV1alpha1Interface { - return &fakesuperherov1alpha1.FakeSuperheroV1alpha1{Fake: &c.Fake} -} diff --git a/test/integration/operator/client/k8s/clientset/versioned/fake/doc.go b/test/integration/operator/client/k8s/clientset/versioned/fake/doc.go deleted file mode 100644 index 9b99e716..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/fake/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated fake clientset. -package fake diff --git a/test/integration/operator/client/k8s/clientset/versioned/fake/register.go b/test/integration/operator/client/k8s/clientset/versioned/fake/register.go deleted file mode 100644 index 7fd9a43c..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/fake/register.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - superherov1alpha1 "github.com/spotahome/kooper/test/integration/operator/apis/superhero/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" -) - -var scheme = runtime.NewScheme() -var codecs = serializer.NewCodecFactory(scheme) -var parameterCodec = runtime.NewParameterCodec(scheme) -var localSchemeBuilder = runtime.SchemeBuilder{ - superherov1alpha1.AddToScheme, -} - -// AddToScheme adds all types of this clientset into the given scheme. This allows composition -// of clientsets, like in: -// -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kubernetes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) -// -// kclientset, _ := kubernetes.NewForConfig(c) -// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) -// -// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types -// correctly. -var AddToScheme = localSchemeBuilder.AddToScheme - -func init() { - v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) - utilruntime.Must(AddToScheme(scheme)) -} diff --git a/test/integration/operator/client/k8s/clientset/versioned/scheme/doc.go b/test/integration/operator/client/k8s/clientset/versioned/scheme/doc.go deleted file mode 100644 index 7dc37561..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/scheme/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// This package contains the scheme of the automatically generated clientset. -package scheme diff --git a/test/integration/operator/client/k8s/clientset/versioned/scheme/register.go b/test/integration/operator/client/k8s/clientset/versioned/scheme/register.go deleted file mode 100644 index c7310d9d..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/scheme/register.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package scheme - -import ( - superherov1alpha1 "github.com/spotahome/kooper/test/integration/operator/apis/superhero/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" -) - -var Scheme = runtime.NewScheme() -var Codecs = serializer.NewCodecFactory(Scheme) -var ParameterCodec = runtime.NewParameterCodec(Scheme) -var localSchemeBuilder = runtime.SchemeBuilder{ - superherov1alpha1.AddToScheme, -} - -// AddToScheme adds all types of this clientset into the given scheme. This allows composition -// of clientsets, like in: -// -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kubernetes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) -// -// kclientset, _ := kubernetes.NewForConfig(c) -// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) -// -// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types -// correctly. -var AddToScheme = localSchemeBuilder.AddToScheme - -func init() { - v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) - utilruntime.Must(AddToScheme(Scheme)) -} diff --git a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/doc.go b/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/doc.go deleted file mode 100644 index df51baa4..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated typed clients. -package v1alpha1 diff --git a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake/doc.go b/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake/doc.go deleted file mode 100644 index 16f44399..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// Package fake has the automatically generated clients. -package fake diff --git a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake/fake_spiderman.go b/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake/fake_spiderman.go deleted file mode 100644 index dbfe5e4d..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake/fake_spiderman.go +++ /dev/null @@ -1,128 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v1alpha1 "github.com/spotahome/kooper/test/integration/operator/apis/superhero/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeSpidermans implements SpidermanInterface -type FakeSpidermans struct { - Fake *FakeSuperheroV1alpha1 - ns string -} - -var spidermansResource = schema.GroupVersionResource{Group: "superhero.comic.kooper", Version: "v1alpha1", Resource: "spidermans"} - -var spidermansKind = schema.GroupVersionKind{Group: "superhero.comic.kooper", Version: "v1alpha1", Kind: "Spiderman"} - -// Get takes name of the spiderman, and returns the corresponding spiderman object, and an error if there is any. -func (c *FakeSpidermans) Get(name string, options v1.GetOptions) (result *v1alpha1.Spiderman, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(spidermansResource, c.ns, name), &v1alpha1.Spiderman{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.Spiderman), err -} - -// List takes label and field selectors, and returns the list of Spidermans that match those selectors. -func (c *FakeSpidermans) List(opts v1.ListOptions) (result *v1alpha1.SpidermanList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(spidermansResource, spidermansKind, c.ns, opts), &v1alpha1.SpidermanList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1alpha1.SpidermanList{ListMeta: obj.(*v1alpha1.SpidermanList).ListMeta} - for _, item := range obj.(*v1alpha1.SpidermanList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested spidermans. -func (c *FakeSpidermans) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(spidermansResource, c.ns, opts)) - -} - -// Create takes the representation of a spiderman and creates it. Returns the server's representation of the spiderman, and an error, if there is any. -func (c *FakeSpidermans) Create(spiderman *v1alpha1.Spiderman) (result *v1alpha1.Spiderman, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(spidermansResource, c.ns, spiderman), &v1alpha1.Spiderman{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.Spiderman), err -} - -// Update takes the representation of a spiderman and updates it. Returns the server's representation of the spiderman, and an error, if there is any. -func (c *FakeSpidermans) Update(spiderman *v1alpha1.Spiderman) (result *v1alpha1.Spiderman, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(spidermansResource, c.ns, spiderman), &v1alpha1.Spiderman{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.Spiderman), err -} - -// Delete takes name of the spiderman and deletes it. Returns an error if one occurs. -func (c *FakeSpidermans) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(spidermansResource, c.ns, name), &v1alpha1.Spiderman{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeSpidermans) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(spidermansResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &v1alpha1.SpidermanList{}) - return err -} - -// Patch applies the patch and returns the patched spiderman. -func (c *FakeSpidermans) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Spiderman, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(spidermansResource, c.ns, name, pt, data, subresources...), &v1alpha1.Spiderman{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.Spiderman), err -} diff --git a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake/fake_superhero_client.go b/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake/fake_superhero_client.go deleted file mode 100644 index 638dc200..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/fake/fake_superhero_client.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v1alpha1 "github.com/spotahome/kooper/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1" - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" -) - -type FakeSuperheroV1alpha1 struct { - *testing.Fake -} - -func (c *FakeSuperheroV1alpha1) Spidermans(namespace string) v1alpha1.SpidermanInterface { - return &FakeSpidermans{c, namespace} -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeSuperheroV1alpha1) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/generated_expansion.go b/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/generated_expansion.go deleted file mode 100644 index e8bda0ed..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/generated_expansion.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -type SpidermanExpansion interface{} diff --git a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/spiderman.go b/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/spiderman.go deleted file mode 100644 index e8e56d87..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/spiderman.go +++ /dev/null @@ -1,174 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - "time" - - v1alpha1 "github.com/spotahome/kooper/test/integration/operator/apis/superhero/v1alpha1" - scheme "github.com/spotahome/kooper/test/integration/operator/client/k8s/clientset/versioned/scheme" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" -) - -// SpidermansGetter has a method to return a SpidermanInterface. -// A group's client should implement this interface. -type SpidermansGetter interface { - Spidermans(namespace string) SpidermanInterface -} - -// SpidermanInterface has methods to work with Spiderman resources. -type SpidermanInterface interface { - Create(*v1alpha1.Spiderman) (*v1alpha1.Spiderman, error) - Update(*v1alpha1.Spiderman) (*v1alpha1.Spiderman, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*v1alpha1.Spiderman, error) - List(opts v1.ListOptions) (*v1alpha1.SpidermanList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Spiderman, err error) - SpidermanExpansion -} - -// spidermans implements SpidermanInterface -type spidermans struct { - client rest.Interface - ns string -} - -// newSpidermans returns a Spidermans -func newSpidermans(c *SuperheroV1alpha1Client, namespace string) *spidermans { - return &spidermans{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the spiderman, and returns the corresponding spiderman object, and an error if there is any. -func (c *spidermans) Get(name string, options v1.GetOptions) (result *v1alpha1.Spiderman, err error) { - result = &v1alpha1.Spiderman{} - err = c.client.Get(). - Namespace(c.ns). - Resource("spidermans"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Spidermans that match those selectors. -func (c *spidermans) List(opts v1.ListOptions) (result *v1alpha1.SpidermanList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1alpha1.SpidermanList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("spidermans"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested spidermans. -func (c *spidermans) Watch(opts v1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("spidermans"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch() -} - -// Create takes the representation of a spiderman and creates it. Returns the server's representation of the spiderman, and an error, if there is any. -func (c *spidermans) Create(spiderman *v1alpha1.Spiderman) (result *v1alpha1.Spiderman, err error) { - result = &v1alpha1.Spiderman{} - err = c.client.Post(). - Namespace(c.ns). - Resource("spidermans"). - Body(spiderman). - Do(). - Into(result) - return -} - -// Update takes the representation of a spiderman and updates it. Returns the server's representation of the spiderman, and an error, if there is any. -func (c *spidermans) Update(spiderman *v1alpha1.Spiderman) (result *v1alpha1.Spiderman, err error) { - result = &v1alpha1.Spiderman{} - err = c.client.Put(). - Namespace(c.ns). - Resource("spidermans"). - Name(spiderman.Name). - Body(spiderman). - Do(). - Into(result) - return -} - -// Delete takes name of the spiderman and deletes it. Returns an error if one occurs. -func (c *spidermans) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("spidermans"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *spidermans) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - var timeout time.Duration - if listOptions.TimeoutSeconds != nil { - timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("spidermans"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Timeout(timeout). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched spiderman. -func (c *spidermans) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Spiderman, err error) { - result = &v1alpha1.Spiderman{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("spidermans"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/superhero_client.go b/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/superhero_client.go deleted file mode 100644 index 9437ac69..00000000 --- a/test/integration/operator/client/k8s/clientset/versioned/typed/superhero/v1alpha1/superhero_client.go +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - v1alpha1 "github.com/spotahome/kooper/test/integration/operator/apis/superhero/v1alpha1" - "github.com/spotahome/kooper/test/integration/operator/client/k8s/clientset/versioned/scheme" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - rest "k8s.io/client-go/rest" -) - -type SuperheroV1alpha1Interface interface { - RESTClient() rest.Interface - SpidermansGetter -} - -// SuperheroV1alpha1Client is used to interact with features provided by the superhero.comic.kooper group. -type SuperheroV1alpha1Client struct { - restClient rest.Interface -} - -func (c *SuperheroV1alpha1Client) Spidermans(namespace string) SpidermanInterface { - return newSpidermans(c, namespace) -} - -// NewForConfig creates a new SuperheroV1alpha1Client for the given config. -func NewForConfig(c *rest.Config) (*SuperheroV1alpha1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - client, err := rest.RESTClientFor(&config) - if err != nil { - return nil, err - } - return &SuperheroV1alpha1Client{client}, nil -} - -// NewForConfigOrDie creates a new SuperheroV1alpha1Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *SuperheroV1alpha1Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new SuperheroV1alpha1Client for the given RESTClient. -func New(c rest.Interface) *SuperheroV1alpha1Client { - return &SuperheroV1alpha1Client{c} -} - -func setConfigDefaults(config *rest.Config) error { - gv := v1alpha1.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/apis" - config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } - - return nil -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *SuperheroV1alpha1Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/test/integration/operator/doc.go b/test/integration/operator/doc.go deleted file mode 100644 index 18b97f2b..00000000 --- a/test/integration/operator/doc.go +++ /dev/null @@ -1 +0,0 @@ -package operator diff --git a/test/integration/operator/operator_test.go b/test/integration/operator/operator_test.go deleted file mode 100644 index 5dae6ae8..00000000 --- a/test/integration/operator/operator_test.go +++ /dev/null @@ -1,148 +0,0 @@ -// +build integration - -package operator_test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - - "github.com/spotahome/kooper/client/crd" - "github.com/spotahome/kooper/log" - "github.com/spotahome/kooper/operator" - "github.com/spotahome/kooper/operator/controller" - "github.com/spotahome/kooper/operator/handler" - "github.com/spotahome/kooper/test/integration/helper/cli" - "github.com/spotahome/kooper/test/integration/helper/prepare" - superherov1alpha1 "github.com/spotahome/kooper/test/integration/operator/apis/superhero/v1alpha1" - integrationtestk8scli "github.com/spotahome/kooper/test/integration/operator/client/k8s/clientset/versioned" - apiextensionscli "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" -) - -type spidermanCRD struct { - aexcli apiextensionscli.Interface - crdcli crd.Interface - kubeccli kubernetes.Interface - integrationtestk8scli integrationtestk8scli.Interface -} - -func (s *spidermanCRD) GetListerWatcher() cache.ListerWatcher { - return &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return s.integrationtestk8scli.SuperheroV1alpha1().Spidermans("").List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return s.integrationtestk8scli.SuperheroV1alpha1().Spidermans("").Watch(options) - }, - } -} - -func (s *spidermanCRD) GetObject() runtime.Object { - return &superherov1alpha1.Spiderman{} -} - -// podTerminatorCRD satisfies resource.crd interface. -func (s *spidermanCRD) Initialize() error { - crd := crd.Conf{ - Kind: superherov1alpha1.SpidermanKind, - NamePlural: superherov1alpha1.SpidermanNamePlural, - ShortNames: superherov1alpha1.SpidermanShortNames, - Group: superherov1alpha1.SchemeGroupVersion.Group, - Version: superherov1alpha1.SchemeGroupVersion.Version, - Scope: superherov1alpha1.SpidermanScope, - } - - return s.crdcli.EnsurePresent(crd) -} - -func (s *spidermanCRD) deleteCRD() error { - crdName := fmt.Sprintf("%s.%s", superherov1alpha1.SpidermanNamePlural, superherov1alpha1.SchemeGroupVersion.Group) - return s.aexcli.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(crdName, &metav1.DeleteOptions{}) -} - -// TestCRDRegister will test the CRD is registered on the cluster. -func TestCRDRegister(t *testing.T) { - tests := []struct { - name string - }{ - { - name: "Starting the operator should register the CRD.", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - assert := assert.New(t) - - resync := 30 * time.Second - stopC := make(chan struct{}) - - // Create the kubernetes client. - k8scli, aexcli, itcli, err := cli.GetK8sClients("") - require.NoError(err, "kubernetes client is required") - - // Prepare the environment on the cluster. - prep := prepare.New(k8scli, t) - prep.SetUp() - defer prep.TearDown() - - // Create the CRD. - spcrd := &spidermanCRD{ - aexcli: aexcli, - crdcli: crd.NewClient(aexcli, log.Dummy), - kubeccli: k8scli, - integrationtestk8scli: itcli, - } - - // Create the handler. - hl := &handler.HandlerFunc{ - AddFunc: func(_ context.Context, obj runtime.Object) error { - return nil - }, - DeleteFunc: func(_ context.Context, id string) error { - return nil - }, - } - - // Create a controller. - ctrl := controller.NewSequential(resync, hl, spcrd, nil, log.Dummy) - require.NotNil(ctrl, "controller is required") - - // Check no CRD. - _, err = itcli.Discovery().ServerResourcesForGroupVersion(superherov1alpha1.SchemeGroupVersion.String()) - require.Error(err, "the resource shouldn't be registered") - // At the end of the test the resource shouldn't be there. - defer spcrd.deleteCRD() - - // Starting the operator should register the CRD. - op := operator.NewOperator(spcrd, ctrl, log.Dummy) - go op.Run(stopC) - // Stop operator when the test is done. - defer func() { - close(stopC) - }() - - // Wait some time until the registration takes effect. - <-time.After(2 * time.Second) - - // Check. - rl, err := itcli.Discovery().ServerResourcesForGroupVersion(superherov1alpha1.SchemeGroupVersion.String()) - if assert.NoError(err) { - // Check the only resource available is spiderman. - rr := rl.APIResources - assert.Len(rr, 1) - assert.Equal(superherov1alpha1.SpidermanKind, rr[0].Kind) - } - }) - } -}