├── .dockerignore ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── groupversion_info.go │ ├── stack_types.go │ └── zz_generated.deepcopy.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ └── cloudformation.linki.space_stacks.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_stacks.yaml │ │ └── webhook_in_stacks.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── stack_editor_role.yaml │ └── stack_viewer_role.yaml ├── samples │ ├── cfs-my-bucket-tags.yaml │ ├── cfs-my-bucket-v1.yaml │ ├── cfs-my-bucket-v2.yaml │ ├── cfs-my-bucket-v3.yaml │ ├── cfs-my-bucket-v4.yaml │ └── kustomization.yaml └── scorecard │ ├── bases │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ ├── basic.config.yaml │ └── olm.config.yaml ├── controllers ├── cloudformation_helper.go ├── stack_controller.go ├── stack_follower.go └── suite_test.go ├── docs └── img │ ├── stack-create.png │ ├── stack-delete.png │ ├── stack-tags.png │ └── stack-update.png ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── helm ├── README.md └── cloudformation-operator │ ├── .helmignore │ ├── Chart.yaml │ ├── crds │ └── stack.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── configmap.yaml │ ├── deployment.yaml │ ├── role.yaml │ ├── rolebinding.yaml │ ├── service.yaml │ └── serviceaccount.yaml │ └── values.yaml ├── main.go └── renovate.json /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore all files which are not go type 3 | !**/*.go 4 | !**/*.mod 5 | !**/*.sum 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # editor and IDE paraphernalia 2 | .idea 3 | *.swp 4 | *.swo 5 | # Temporary Build Files 6 | build/_output 7 | build/_test 8 | # Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 9 | ### Emacs ### 10 | # -*- mode: gitignore; -*- 11 | *~ 12 | \#*\# 13 | /.emacs.desktop 14 | /.emacs.desktop.lock 15 | *.elc 16 | auto-save-list 17 | tramp 18 | .\#* 19 | # Org-mode 20 | .org-id-locations 21 | *_archive 22 | # flymake-mode 23 | *_flymake.* 24 | # eshell files 25 | /eshell/history 26 | /eshell/lastdir 27 | # elpa packages 28 | /elpa/ 29 | # reftex files 30 | *.rel 31 | # AUCTeX auto folder 32 | /auto/ 33 | # cask packages 34 | .cask/ 35 | dist/ 36 | # Flycheck 37 | flycheck_*.el 38 | # server auth directory 39 | /server/ 40 | # projectiles files 41 | .projectile 42 | projectile-bookmarks.eld 43 | # directory configuration 44 | .dir-locals.el 45 | # saveplace 46 | places 47 | # url cache 48 | url/cache/ 49 | # cedet 50 | ede-projects.el 51 | # smex 52 | smex-items 53 | # company-statistics 54 | company-statistics-cache.el 55 | # anaconda-mode 56 | anaconda-mode/ 57 | ### Go ### 58 | # Binaries for programs and plugins 59 | *.exe 60 | *.exe~ 61 | *.dll 62 | *.so 63 | *.dylib 64 | bin 65 | testbin/* 66 | # Test binary, build with 'go test -c' 67 | *.test 68 | # Output of the go coverage tool, specifically when used with LiteIDE 69 | *.out 70 | ### Vim ### 71 | # swap 72 | .sw[a-p] 73 | .*.sw[a-p] 74 | # session 75 | Session.vim 76 | # temporary 77 | .netrwhist 78 | # auto-generated tag files 79 | tags 80 | ### VisualStudioCode ### 81 | .vscode/* 82 | .history 83 | # End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 84 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | 3 | os: 4 | - linux 5 | 6 | language: go 7 | 8 | go: 9 | - "1.15" 10 | - tip 11 | 12 | env: 13 | - GO111MODULE=on 14 | 15 | script: 16 | - go build main.go 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM docker.io/library/golang:1.15 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY main.go main.go 14 | COPY api/ api/ 15 | COPY controllers/ controllers/ 16 | 17 | # Build 18 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go 19 | 20 | # Use distroless as minimal base image to package the manager binary 21 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 22 | FROM gcr.io/distroless/static:nonroot 23 | WORKDIR / 24 | COPY --from=builder /workspace/manager . 25 | USER 65532:65532 26 | 27 | ENTRYPOINT ["/manager"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Martin Linkhorst 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Current Operator version 2 | VERSION ?= 0.9.0 3 | # Default bundle image tag 4 | BUNDLE_IMG ?= controller-bundle:$(VERSION) 5 | # Options for 'bundle-build' 6 | ifneq ($(origin CHANNELS), undefined) 7 | BUNDLE_CHANNELS := --channels=$(CHANNELS) 8 | endif 9 | ifneq ($(origin DEFAULT_CHANNEL), undefined) 10 | BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) 11 | endif 12 | BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) 13 | 14 | # Image URL to use all building/pushing image targets 15 | IMG ?= controller:latest 16 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 17 | CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false" 18 | 19 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 20 | ifeq (,$(shell go env GOBIN)) 21 | GOBIN=$(shell go env GOPATH)/bin 22 | else 23 | GOBIN=$(shell go env GOBIN) 24 | endif 25 | 26 | all: manager 27 | 28 | # Run tests 29 | ENVTEST_ASSETS_DIR=$(shell pwd)/testbin 30 | test: generate fmt vet manifests 31 | mkdir -p ${ENVTEST_ASSETS_DIR} 32 | test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh 33 | source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out 34 | 35 | # Build manager binary 36 | manager: generate fmt vet 37 | go build -o bin/manager main.go 38 | 39 | # Run against the configured Kubernetes cluster in ~/.kube/config 40 | run: generate fmt vet manifests 41 | go run ./main.go ${OPERATOR_FLAGS} 42 | 43 | # Install CRDs into a cluster 44 | install: manifests kustomize 45 | $(KUSTOMIZE) build config/crd | kubectl apply -f - 46 | 47 | # Uninstall CRDs from a cluster 48 | uninstall: manifests kustomize 49 | $(KUSTOMIZE) build config/crd | kubectl delete -f - 50 | 51 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 52 | deploy: manifests kustomize 53 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 54 | $(KUSTOMIZE) build config/default | kubectl apply -f - 55 | 56 | # UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config 57 | undeploy: 58 | $(KUSTOMIZE) build config/default | kubectl delete -f - 59 | 60 | # Generate manifests e.g. CRD, RBAC etc. 61 | manifests: controller-gen 62 | $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases 63 | 64 | # Run go fmt against code 65 | fmt: 66 | go fmt ./... 67 | 68 | # Run go vet against code 69 | vet: 70 | go vet ./... 71 | 72 | # Generate code 73 | generate: controller-gen 74 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 75 | 76 | # Build the docker image 77 | docker-build: test 78 | docker build -t ${IMG} . 79 | 80 | # Push the docker image 81 | docker-push: 82 | docker push ${IMG} 83 | 84 | # Download controller-gen locally if necessary 85 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen 86 | controller-gen: 87 | $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) 88 | 89 | # Download kustomize locally if necessary 90 | KUSTOMIZE = $(shell pwd)/bin/kustomize 91 | kustomize: 92 | $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) 93 | 94 | # go-get-tool will 'go get' any package $2 and install it to $1. 95 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) 96 | define go-get-tool 97 | @[ -f $(1) ] || { \ 98 | set -e ;\ 99 | TMP_DIR=$$(mktemp -d) ;\ 100 | cd $$TMP_DIR ;\ 101 | go mod init tmp ;\ 102 | echo "Downloading $(2)" ;\ 103 | GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ 104 | rm -rf $$TMP_DIR ;\ 105 | } 106 | endef 107 | 108 | # Generate bundle manifests and metadata, then validate generated files. 109 | .PHONY: bundle 110 | bundle: manifests kustomize 111 | operator-sdk generate kustomize manifests -q 112 | cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) 113 | $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) 114 | operator-sdk bundle validate ./bundle 115 | 116 | # Build the bundle image. 117 | .PHONY: bundle-build 118 | bundle-build: 119 | docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . 120 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: cloudformation.linki.space 2 | layout: go.kubebuilder.io/v3 3 | projectName: cloudformation-operator 4 | repo: github.com/linki/cloudformation-operator 5 | resources: 6 | - crdVersion: v1 7 | kind: Stack 8 | version: v1alpha1 9 | version: 3-alpha 10 | plugins: 11 | manifests.sdk.operatorframework.io/v2: {} 12 | scorecard.sdk.operatorframework.io/v2: {} 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub release](https://img.shields.io/github/release/linki/cloudformation-operator.svg)](https://github.com/linki/cloudformation-operator/releases) 2 | [![Docker Repository on Quay](https://quay.io/repository/linki/cloudformation-operator/status "Docker Repository on Quay")](https://quay.io/repository/linki/cloudformation-operator) 3 | 4 | # cloudformation-operator 5 | 6 | A Kubernetes operator for managing CloudFormation stacks via `kubectl` and a custom resource definition. 7 | 8 | **Warning: this project is in alpha state. It should only be used to try out the demo and get the general idea.** 9 | 10 | **This version uses the new operator-sdk. It's untested and may not work correctly** 11 | 12 | # Deploy to a cluster 13 | 14 | You need API access to a cluster running at least Kubernetes v1.17.0+. 15 | 16 | Start the CloudFormation operator in your cluster by using the provided manifests: 17 | 18 | ```console 19 | $ make deploy IMG=quay.io/linki/cloudformation-operator:latest 20 | ``` 21 | 22 | Modify the `region` flag to match your cluster's. 23 | 24 | Additionally you need to make sure that the operator Pod has enough AWS IAM permissions to create, update and delete CloudFormation stacks as well as permission to modify any resources that are part of the CloudFormation stacks you intend to deploy. In order to follow the example below it needs access to CloudFormation as well as S3. 25 | 26 | The operator will require an IAM role or user credentials. 27 | Use the following Policy document as a guideline in order to follow the tutorial: 28 | 29 | ```yaml 30 | MyIAMRole: 31 | Properties: 32 | ... 33 | Policies: 34 | - PolicyDocument: 35 | Statement: 36 | - {Action: 'cloudformation:*', Effect: Allow, Resource: '*'} 37 | - {Action: 's3:*', Effect: Allow, Resource: '*'} 38 | Version: '2012-10-17' 39 | ... 40 | ``` 41 | 42 | The operator will usually use the IAM role of the EC2 instance it's running on, so you have to add those permissions to that role. If you're using [Kube2IAM](https://github.com/jtblin/kube2iam) or similar and give your Pod a dedicated IAM role then you have to add the permissions to that role. 43 | 44 | Once running the operator should print some output but shouldn't actually do anything at this point. Leave it running, keep watching its logs and continue with the steps below. 45 | 46 | # Demo 47 | 48 | ## Create stack 49 | 50 | Currently you don't have any stacks. 51 | 52 | ```console 53 | $ kubectl get stacks 54 | No resources found. 55 | ``` 56 | 57 | Let's create a simple one that manages an S3 bucket: 58 | 59 | ```yaml 60 | apiVersion: cloudformation.linki.space/v1alpha1 61 | kind: Stack 62 | metadata: 63 | name: my-bucket 64 | spec: 65 | template: | 66 | --- 67 | AWSTemplateFormatVersion: '2010-09-09' 68 | 69 | Resources: 70 | S3Bucket: 71 | Type: AWS::S3::Bucket 72 | Properties: 73 | VersioningConfiguration: 74 | Status: Suspended 75 | ``` 76 | 77 | The Stack resource's definition looks a lot like any other Kubernetes resource manifest. 78 | The `spec` section describes an attribute called `template` which contains a regular CloudFormation template. 79 | 80 | Go ahead and submit the stack definition to your cluster: 81 | 82 | ```console 83 | $ kubectl apply -f config/samples/cfs-my-bucket-v1.yaml 84 | stack "my-bucket" created 85 | $ kubectl get stacks 86 | NAME AGE 87 | my-bucket 21s 88 | ``` 89 | 90 | Open your AWS CloudFormation console and find your new stack. 91 | 92 | ![Create stack](docs/img/stack-create.png) 93 | 94 | Once the CloudFormation stack is created check that your S3 bucket was created as well. 95 | 96 | The operator will write back additional information about the CloudFormation Stack to your Kubernetes resource's `status` section, e.g. the `stackID`: 97 | 98 | ```console 99 | $ kubectl get stacks my-bucket -o yaml 100 | spec: 101 | template: 102 | ... 103 | status: 104 | stackID: arn:aws:cloudformation:eu-central-1:123456789012:stack/my-bucket/327b7d3c-f27b-4b94-8d17-92a1d9da85ab 105 | ``` 106 | 107 | Voilà, you just created a CloudFormation stack by only talking to Kubernetes. 108 | 109 | ## Update stack 110 | 111 | You can also update your stack: Let's change the `VersioningConfiguration` from `Suspended` to `Enabled`: 112 | 113 | ```yaml 114 | apiVersion: cloudformation.linki.space/v1alpha1 115 | kind: Stack 116 | metadata: 117 | name: my-bucket 118 | spec: 119 | template: | 120 | --- 121 | AWSTemplateFormatVersion: '2010-09-09' 122 | 123 | Resources: 124 | S3Bucket: 125 | Type: AWS::S3::Bucket 126 | Properties: 127 | VersioningConfiguration: 128 | Status: Enabled 129 | ``` 130 | 131 | As with most Kubernetes resources you can update your `Stack` resource by applying a changed manifest to your Kubernetes cluster or by using `kubectl edit stack my-stack`. 132 | 133 | ```console 134 | $ kubectl apply -f config/samples/cfs-my-bucket-v2.yaml 135 | stack "my-bucket" configured 136 | ``` 137 | 138 | Wait until the operator discovered and executed the change, then look at your AWS CloudFormation console again and find your stack being updated, yay. 139 | 140 | ![Update stack](docs/img/stack-update.png) 141 | 142 | ## Tags 143 | 144 | You may want to assign tags to your CloudFormation stacks. The tags added to a CloudFormation stack will be propagated to the managed resources. This feature may be useful in multiple cases, for example, to distinguish resources at billing report. Current operator provides two ways to assign tags: 145 | - `--tag` command line argument or `AWS_TAGS` environment variable which allows setting default tags for all resources managed by the operator. The format is `--tag=foo=bar --tag=wambo=baz` on the command line or with a line break when specifying as an env var. (e.g. in zsh: `AWS_TAGS="foo=bar"$'\n'"wambo=baz"`) 146 | - `tags` parameter at kubernetes resource spec: 147 | ```yaml 148 | apiVersion: cloudformation.linki.space/v1alpha1 149 | kind: Stack 150 | metadata: 151 | name: my-bucket 152 | spec: 153 | tags: 154 | foo: dataFromStack 155 | template: | 156 | --- 157 | AWSTemplateFormatVersion: '2010-09-09' 158 | 159 | Resources: 160 | S3Bucket: 161 | Type: AWS::S3::Bucket 162 | Properties: 163 | VersioningConfiguration: 164 | Status: Enabled 165 | ``` 166 | 167 | Resource-specific tags have precedence over the default tags. Thus if a tag is defined at command-line arguments and for a `Stack` resource, the value from the `Stack` resource will be used. 168 | 169 | If we run the operation and a `Stack` resource with the described above examples, we'll see such picture: 170 | 171 | ![Stack tags](docs/img/stack-tags.png) 172 | 173 | ## Parameters 174 | 175 | However, often you'll want to extract dynamic values out of your CloudFormation stack template into so called `Parameters` so that your template itself doesn't change that often and, well, is really a *template*. 176 | 177 | Let's extract the `VersioningConfiguration` into a parameter: 178 | 179 | ```yaml 180 | apiVersion: cloudformation.linki.space/v1alpha1 181 | kind: Stack 182 | metadata: 183 | name: my-bucket 184 | spec: 185 | parameters: 186 | VersioningConfiguration: Enabled 187 | template: | 188 | --- 189 | AWSTemplateFormatVersion: '2010-09-09' 190 | 191 | Parameters: 192 | VersioningConfiguration: 193 | Type: String 194 | Default: none 195 | AllowedValues: 196 | - "Enabled" 197 | - "Suspended" 198 | 199 | Resources: 200 | S3Bucket: 201 | Type: AWS::S3::Bucket 202 | Properties: 203 | VersioningConfiguration: 204 | Status: 205 | Ref: VersioningConfiguration 206 | ``` 207 | 208 | and apply it to your cluster: 209 | 210 | ```console 211 | $ kubectl apply -f config/samples/cfs-my-bucket-v3.yaml 212 | stack "my-bucket" configured 213 | ``` 214 | 215 | Since we changed the template a little this will update your CloudFormation stack. However, since we didn't actually change anything because we injected the same `VersioningConfiguration` value as before, your S3 bucket shouldn't change. 216 | 217 | Any CloudFormation parameters defined in the CloudFormation template can be specified in the `Stack` resource's `spec.parameters` section. It's a simple key/value map. 218 | 219 | ## Outputs 220 | 221 | Furthermore, CloudFormation supports so called `Outputs`. These can be used for dynamic values that are only known after a stack has been created. 222 | In our example, we don't define a particular S3 bucket name but instead let AWS generate one for us. 223 | 224 | Let's change our CloudFormation template to expose the generated bucket name via an `Output`: 225 | 226 | ```yaml 227 | apiVersion: cloudformation.linki.space/v1alpha1 228 | kind: Stack 229 | metadata: 230 | name: my-bucket 231 | spec: 232 | parameters: 233 | VersioningConfiguration: Enabled 234 | template: | 235 | --- 236 | AWSTemplateFormatVersion: '2010-09-09' 237 | 238 | Parameters: 239 | VersioningConfiguration: 240 | Type: String 241 | Default: none 242 | AllowedValues: 243 | - "Enabled" 244 | - "Suspended" 245 | 246 | Resources: 247 | S3Bucket: 248 | Type: AWS::S3::Bucket 249 | Properties: 250 | VersioningConfiguration: 251 | Status: 252 | Ref: VersioningConfiguration 253 | 254 | Outputs: 255 | BucketName: 256 | Value: !Ref 'S3Bucket' 257 | Description: Name of the sample Amazon S3 bucket. 258 | ``` 259 | 260 | Apply the change to our cluster and wait until the operator has successfully updated the CloudFormation stack. 261 | 262 | ```console 263 | $ kubectl apply -f config/samples/cfs-my-bucket-v4.yaml 264 | stack "my-bucket" configured 265 | ``` 266 | 267 | Every `Output` you define will be available in your Kubernetes resource's `status` section under the `outputs` field as a key/value map. 268 | 269 | Let's check the name of our S3 bucket: 270 | 271 | ```console 272 | $ kubectl get stacks my-bucket -o yaml 273 | spec: 274 | template: 275 | ... 276 | status: 277 | stackID: ... 278 | outputs: 279 | BucketName: my-bucket-s3bucket-tarusnslfnsj 280 | ``` 281 | 282 | In the template we defined an `Output` called `BucketName` that should contain the name of our bucket after stack creation. Looking up the corresponding value under `.status.outputs[BucketName]` reveals that our bucket was named `my-bucket-s3bucket-tarusnslfnsj`. 283 | 284 | ## Delete stack 285 | 286 | The operator captures the whole lifecycle of a CloudFormation stack. So if you delete the resource from Kubernetes, the operator will teardown the CloudFormation stack as well. Let's do that now: 287 | 288 | ```console 289 | $ kubectl delete stack my-bucket 290 | stack "my-bucket" deleted 291 | ``` 292 | 293 | Check your CloudFormation console once more and validate that your stack as well as your S3 bucket were deleted. 294 | 295 | ![Delete stack](docs/img/stack-delete.png) 296 | 297 | # Command-line arguments 298 | 299 | Argument | Environment variable | Default value | Description 300 | ---------|----------------------|---------------|------------ 301 | assume-role | | | Assume AWS role when defined. Useful for stacks in another AWS account. Specify the full ARN, e.g. `arn:aws:iam::123456789:role/cloudformation-operator` 302 | capability | | | Enable specified capabilities for all stacks managed by the operator instance. Current parameter can be used multiple times. For example: `--capability CAPABILITY_NAMED_IAM --capability CAPABILITY_IAM`. Or with a line break when specifying as an environment variable: `AWS_CAPABILITIES=CAPABILITY_IAM$'\n'CAPABILITY_NAMED_IAM` 303 | dry-run | | | If true, don't actually do anything. 304 | tag ... | | | Default tags which should be applied for all stacks. The format is `--tag=foo=bar --tag=wambo=baz` on the command line or with a line break when specifying as an env var. (e.g. in zsh: `AWS_TAGS="foo=bar"$'\n'"wambo=baz"`) 305 | namespace | WATCH_NAMESPACE | default | The Kubernetes namespace to watch 306 | region | | | The AWS region to use 307 | 308 | # Cleanup 309 | 310 | Clean up the resources: 311 | 312 | ```console 313 | $ make undeploy 314 | ``` 315 | 316 | # Build and run locally 317 | 318 | This project uses the [operator sdk](https://github.com/operator-framework/operator-sdk). 319 | 320 | ```console 321 | $ make 322 | $ WATCH_NAMESPACE=default KUBERNETES_CONFIG=~/.kube/config make run OPERATOR_FLAGS="--region eu-central-1" 323 | ``` 324 | 325 | ## Build the docker image 326 | 327 | ```console 328 | $ make docker-build quay.io/linki/cloudformation-operator:latest 329 | $ make docker-push quay.io/linki/cloudformation-operator:latest 330 | ``` 331 | 332 | ## Test it locally 333 | 334 | You can use `OPERATOR_FLAGS` to pass in flags using the operator-sdk. 335 | 336 | Assuming you are using minikube: 337 | 338 | ```console 339 | $ minikube start # you will be have a kubeconfig read to use by cloudformation operator 340 | $ export AWS_PROFILE=my_profile # setup your aws config 341 | $ cd $GOPATH/src/github.com/linki/cloudformation-operator 342 | $ # run cloudformation operator based on previous settings and env vars 343 | $ WATCH_NAMESPACE=staging make run OPERATOR_FLAGS="--dry-run=true --region=eu-central-1" 344 | I0122 16:31:14.509064 195514 request.go:645] Throttling request took 1.027790903s, request: GET:https://api.crc.testing:6443/apis/template.openshift.io/v1?timeout=32s 345 | 2021-01-22T16:31:15.863-0500 INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"} 346 | 2021-01-22T16:31:15.864-0500 INFO setup 347 | 2021-01-22T16:31:15.864-0500 INFO setup starting manager 348 | 2021-01-22T16:31:15.864-0500 INFO controller-runtime.manager starting metrics server {"path": "/metrics"} 349 | 2021-01-22T16:31:15.864-0500 INFO controller-runtime.manager.controller.stack Starting EventSource {"reconciler group": "cloudformation.linki.space", "reconciler kind": "Stack", "source": "kind source: /, Kind="} 350 | 2021-01-22T16:31:15.965-0500 INFO controller-runtime.manager.controller.stack Starting Controller {"reconciler group": "cloudformation.linki.space", "reconciler kind": "Stack"} 351 | 2021-01-22T16:31:15.965-0500 INFO controller-runtime.manager.controller.stack Starting workers {"reconciler group": "cloudformation.linki.space", "reconciler kind": "Stack", "worker count": 1} 352 | ``` 353 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Martin Linkhorst 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | // Package v1alpha1 contains API Schema definitions for the v1alpha1 API group 26 | // +kubebuilder:object:generate=true 27 | // +groupName=cloudformation.linki.space 28 | package v1alpha1 29 | 30 | import ( 31 | "k8s.io/apimachinery/pkg/runtime/schema" 32 | "sigs.k8s.io/controller-runtime/pkg/scheme" 33 | ) 34 | 35 | var ( 36 | // GroupVersion is group version used to register these objects 37 | GroupVersion = schema.GroupVersion{Group: "cloudformation.linki.space", Version: "v1alpha1"} 38 | 39 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 40 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 41 | 42 | // AddToScheme adds the types in this group-version to the given scheme. 43 | AddToScheme = SchemeBuilder.AddToScheme 44 | ) 45 | -------------------------------------------------------------------------------- /api/v1alpha1/stack_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Martin Linkhorst 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package v1alpha1 26 | 27 | import ( 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | ) 30 | 31 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 32 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 33 | // Important: Run "make" to regenerate code after modifying this file 34 | 35 | // Defines the desired state of Stack 36 | type StackSpec struct { 37 | // +kubebuilder:validation:Optional 38 | Parameters map[string]string `json:"parameters,omitempty"` 39 | // +kubebuilder:validation:Optional 40 | Tags map[string]string `json:"tags,omitempty"` 41 | Template string `json:"template"` 42 | } 43 | 44 | // Defines the observed state of Stack 45 | type StackStatus struct { 46 | StackID string `json:"stackID"` 47 | // +kubebuilder:validation:Optional 48 | StackStatus string `json:"stackStatus"` 49 | // +kubebuilder:validation:Optional 50 | // +nullable 51 | CreatedTime metav1.Time `json:"createdTime,omitEmpty"` 52 | // +kubebuilder:validation:Optional 53 | // +nullable 54 | UpdatedTime metav1.Time `json:"updatedTime,omitEmpty"` 55 | // +kubebuilder:validation:Optional 56 | // +nullable 57 | Outputs map[string]string `json:"outputs,omitEmpty"` 58 | // +kubebuilder:validation:Optional 59 | // +nullable 60 | Resources []StackResource `json:"resources,omitEmpty"` 61 | } 62 | 63 | // Defines a resource provided/managed by a Stack and its current state 64 | type StackResource struct { 65 | LogicalId string `json:"logicalID"` 66 | PhysicalId string `json:"physicalID"` 67 | Type string `json:"type"` 68 | Status string `json:"status"` 69 | // +kubebuilder:validation:Optional 70 | StatusReason string `json:"statusReason,omitEmpty"` 71 | } 72 | 73 | // +kubebuilder:object:root=true 74 | // +kubebuilder:subresource:status 75 | 76 | // Stack is the Schema for the stacks API 77 | type Stack struct { 78 | metav1.TypeMeta `json:",inline"` 79 | metav1.ObjectMeta `json:"metadata,omitempty"` 80 | 81 | Spec StackSpec `json:"spec,omitempty"` 82 | Status StackStatus `json:"status,omitempty"` 83 | } 84 | 85 | // +kubebuilder:object:root=true 86 | 87 | // StackList contains a list of Stack 88 | type StackList struct { 89 | metav1.TypeMeta `json:",inline"` 90 | metav1.ListMeta `json:"metadata,omitempty"` 91 | Items []Stack `json:"items"` 92 | } 93 | 94 | func init() { 95 | SchemeBuilder.Register(&Stack{}, &StackList{}) 96 | } 97 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | MIT License 5 | 6 | Copyright (c) 2018 Martin Linkhorst 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | // Code generated by controller-gen. DO NOT EDIT. 28 | 29 | package v1alpha1 30 | 31 | import ( 32 | runtime "k8s.io/apimachinery/pkg/runtime" 33 | ) 34 | 35 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 36 | func (in *Stack) DeepCopyInto(out *Stack) { 37 | *out = *in 38 | out.TypeMeta = in.TypeMeta 39 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 40 | in.Spec.DeepCopyInto(&out.Spec) 41 | in.Status.DeepCopyInto(&out.Status) 42 | } 43 | 44 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Stack. 45 | func (in *Stack) DeepCopy() *Stack { 46 | if in == nil { 47 | return nil 48 | } 49 | out := new(Stack) 50 | in.DeepCopyInto(out) 51 | return out 52 | } 53 | 54 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 55 | func (in *Stack) DeepCopyObject() runtime.Object { 56 | if c := in.DeepCopy(); c != nil { 57 | return c 58 | } 59 | return nil 60 | } 61 | 62 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 63 | func (in *StackList) DeepCopyInto(out *StackList) { 64 | *out = *in 65 | out.TypeMeta = in.TypeMeta 66 | in.ListMeta.DeepCopyInto(&out.ListMeta) 67 | if in.Items != nil { 68 | in, out := &in.Items, &out.Items 69 | *out = make([]Stack, len(*in)) 70 | for i := range *in { 71 | (*in)[i].DeepCopyInto(&(*out)[i]) 72 | } 73 | } 74 | } 75 | 76 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StackList. 77 | func (in *StackList) DeepCopy() *StackList { 78 | if in == nil { 79 | return nil 80 | } 81 | out := new(StackList) 82 | in.DeepCopyInto(out) 83 | return out 84 | } 85 | 86 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 87 | func (in *StackList) DeepCopyObject() runtime.Object { 88 | if c := in.DeepCopy(); c != nil { 89 | return c 90 | } 91 | return nil 92 | } 93 | 94 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 95 | func (in *StackResource) DeepCopyInto(out *StackResource) { 96 | *out = *in 97 | } 98 | 99 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StackResource. 100 | func (in *StackResource) DeepCopy() *StackResource { 101 | if in == nil { 102 | return nil 103 | } 104 | out := new(StackResource) 105 | in.DeepCopyInto(out) 106 | return out 107 | } 108 | 109 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 110 | func (in *StackSpec) DeepCopyInto(out *StackSpec) { 111 | *out = *in 112 | if in.Parameters != nil { 113 | in, out := &in.Parameters, &out.Parameters 114 | *out = make(map[string]string, len(*in)) 115 | for key, val := range *in { 116 | (*out)[key] = val 117 | } 118 | } 119 | if in.Tags != nil { 120 | in, out := &in.Tags, &out.Tags 121 | *out = make(map[string]string, len(*in)) 122 | for key, val := range *in { 123 | (*out)[key] = val 124 | } 125 | } 126 | } 127 | 128 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StackSpec. 129 | func (in *StackSpec) DeepCopy() *StackSpec { 130 | if in == nil { 131 | return nil 132 | } 133 | out := new(StackSpec) 134 | in.DeepCopyInto(out) 135 | return out 136 | } 137 | 138 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 139 | func (in *StackStatus) DeepCopyInto(out *StackStatus) { 140 | *out = *in 141 | in.CreatedTime.DeepCopyInto(&out.CreatedTime) 142 | in.UpdatedTime.DeepCopyInto(&out.UpdatedTime) 143 | if in.Outputs != nil { 144 | in, out := &in.Outputs, &out.Outputs 145 | *out = make(map[string]string, len(*in)) 146 | for key, val := range *in { 147 | (*out)[key] = val 148 | } 149 | } 150 | if in.Resources != nil { 151 | in, out := &in.Resources, &out.Resources 152 | *out = make([]StackResource, len(*in)) 153 | copy(*out, *in) 154 | } 155 | } 156 | 157 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StackStatus. 158 | func (in *StackStatus) DeepCopy() *StackStatus { 159 | if in == nil { 160 | return nil 161 | } 162 | out := new(StackStatus) 163 | in.DeepCopyInto(out) 164 | return out 165 | } 166 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | name: selfsigned-issuer 8 | namespace: system 9 | spec: 10 | selfSigned: {} 11 | --- 12 | apiVersion: cert-manager.io/v1 13 | kind: Certificate 14 | metadata: 15 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 16 | namespace: system 17 | spec: 18 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 19 | dnsNames: 20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 22 | issuerRef: 23 | kind: Issuer 24 | name: selfsigned-issuer 25 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 26 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/bases/cloudformation.linki.space_stacks.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.4.1 8 | creationTimestamp: null 9 | name: stacks.cloudformation.linki.space 10 | spec: 11 | group: cloudformation.linki.space 12 | names: 13 | kind: Stack 14 | listKind: StackList 15 | plural: stacks 16 | singular: stack 17 | scope: Namespaced 18 | versions: 19 | - name: v1alpha1 20 | schema: 21 | openAPIV3Schema: 22 | description: Stack is the Schema for the stacks API 23 | properties: 24 | apiVersion: 25 | description: 'APIVersion defines the versioned schema of this representation 26 | of an object. Servers should convert recognized schemas to the latest 27 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 28 | type: string 29 | kind: 30 | description: 'Kind is a string value representing the REST resource this 31 | object represents. Servers may infer this from the endpoint the client 32 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 33 | type: string 34 | metadata: 35 | type: object 36 | spec: 37 | description: Defines the desired state of Stack 38 | properties: 39 | parameters: 40 | additionalProperties: 41 | type: string 42 | type: object 43 | tags: 44 | additionalProperties: 45 | type: string 46 | type: object 47 | template: 48 | type: string 49 | required: 50 | - template 51 | type: object 52 | status: 53 | description: Defines the observed state of Stack 54 | properties: 55 | createdTime: 56 | format: date-time 57 | nullable: true 58 | type: string 59 | outputs: 60 | additionalProperties: 61 | type: string 62 | nullable: true 63 | type: object 64 | resources: 65 | items: 66 | description: Defines a resource provided/managed by a Stack and 67 | its current state 68 | properties: 69 | logicalID: 70 | type: string 71 | physicalID: 72 | type: string 73 | status: 74 | type: string 75 | statusReason: 76 | type: string 77 | type: 78 | type: string 79 | required: 80 | - logicalID 81 | - physicalID 82 | - status 83 | - type 84 | type: object 85 | nullable: true 86 | type: array 87 | stackID: 88 | type: string 89 | stackStatus: 90 | type: string 91 | updatedTime: 92 | format: date-time 93 | nullable: true 94 | type: string 95 | required: 96 | - stackID 97 | type: object 98 | type: object 99 | served: true 100 | storage: true 101 | subresources: 102 | status: {} 103 | status: 104 | acceptedNames: 105 | kind: "" 106 | plural: "" 107 | conditions: [] 108 | storedVersions: [] 109 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/cloudformation.linki.space_stacks.yaml 6 | # +kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_stacks.yaml 12 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- patches/cainjection_in_stacks.yaml 17 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_stacks.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: stacks.cloudformation.linki.space 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_stacks.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: stacks.cloudformation.linki.space 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: cloudformation-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: cloudformation-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | #- webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | # objref: 51 | # kind: Certificate 52 | # group: cert-manager.io 53 | # version: v1 54 | # name: serving-cert # this name should match the one in certificate.yaml 55 | # fieldref: 56 | # fieldpath: metadata.namespace 57 | #- name: CERTIFICATE_NAME 58 | # objref: 59 | # kind: Certificate 60 | # group: cert-manager.io 61 | # version: v1 62 | # name: serving-cert # this name should match the one in certificate.yaml 63 | #- name: SERVICE_NAMESPACE # namespace of the service 64 | # objref: 65 | # kind: Service 66 | # version: v1 67 | # name: webhook-service 68 | # fieldref: 69 | # fieldpath: metadata.namespace 70 | #- name: SERVICE_NAME 71 | # objref: 72 | # kind: Service 73 | # version: v1 74 | # name: webhook-service 75 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--health-probe-bind-address=:8081" 25 | - "--metrics-bind-address=127.0.0.1:8080" 26 | - "--leader-elect" 27 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: 64032969.cloudformation.linki.space 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: manager-config 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | images: 14 | - name: controller 15 | newName: quay.io/linki/cloudformation-operator 16 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | containers: 26 | - command: 27 | - /manager 28 | env: 29 | - name: AWS_REGION 30 | value: eu-central-1 31 | args: 32 | - --leader-elect 33 | image: controller:latest 34 | name: manager 35 | securityContext: 36 | allowPrivilegeEscalation: false 37 | livenessProbe: 38 | httpGet: 39 | path: /healthz 40 | port: 8081 41 | initialDelaySeconds: 15 42 | periodSeconds: 20 43 | readinessProbe: 44 | httpGet: 45 | path: /readyz 46 | port: 8081 47 | initialDelaySeconds: 5 48 | periodSeconds: 10 49 | resources: 50 | requests: 51 | cpu: 5m 52 | memory: 45Mi 53 | terminationGracePeriodSeconds: 10 54 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | selector: 15 | matchLabels: 16 | control-plane: controller-manager 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 4 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | - auth_proxy_client_clusterrole.yaml 13 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | - coordination.k8s.io 10 | resources: 11 | - configmaps 12 | - leases 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - create 18 | - update 19 | - patch 20 | - delete 21 | - apiGroups: 22 | - "" 23 | resources: 24 | - events 25 | verbs: 26 | - create 27 | - patch 28 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - cloudformation.linki.space 11 | resources: 12 | - stacks 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - cloudformation.linki.space 23 | resources: 24 | - stacks/finalizers 25 | verbs: 26 | - update 27 | - apiGroups: 28 | - cloudformation.linki.space 29 | resources: 30 | - stacks/status 31 | verbs: 32 | - get 33 | - patch 34 | - update 35 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/stack_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit stacks. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: stack-editor-role 6 | rules: 7 | - apiGroups: 8 | - cloudformation.linki.space 9 | resources: 10 | - stacks 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - cloudformation.linki.space 21 | resources: 22 | - stacks/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/stack_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view stacks. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: stack-viewer-role 6 | rules: 7 | - apiGroups: 8 | - cloudformation.linki.space 9 | resources: 10 | - stacks 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - cloudformation.linki.space 17 | resources: 18 | - stacks/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/samples/cfs-my-bucket-tags.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cloudformation.linki.space/v1alpha1 2 | kind: Stack 3 | metadata: 4 | name: my-bucket 5 | spec: 6 | tags: 7 | foo: dataFromStack 8 | template: | 9 | --- 10 | AWSTemplateFormatVersion: '2010-09-09' 11 | 12 | Resources: 13 | S3Bucket: 14 | Type: AWS::S3::Bucket 15 | Properties: 16 | VersioningConfiguration: 17 | Status: Suspended 18 | -------------------------------------------------------------------------------- /config/samples/cfs-my-bucket-v1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cloudformation.linki.space/v1alpha1 2 | kind: Stack 3 | metadata: 4 | name: my-bucket 5 | spec: 6 | template: | 7 | --- 8 | AWSTemplateFormatVersion: '2010-09-09' 9 | 10 | Resources: 11 | S3Bucket: 12 | Type: AWS::S3::Bucket 13 | Properties: 14 | VersioningConfiguration: 15 | Status: Suspended 16 | -------------------------------------------------------------------------------- /config/samples/cfs-my-bucket-v2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cloudformation.linki.space/v1alpha1 2 | kind: Stack 3 | metadata: 4 | name: my-bucket 5 | spec: 6 | template: | 7 | --- 8 | AWSTemplateFormatVersion: '2010-09-09' 9 | 10 | Resources: 11 | S3Bucket: 12 | Type: AWS::S3::Bucket 13 | Properties: 14 | VersioningConfiguration: 15 | Status: Enabled 16 | -------------------------------------------------------------------------------- /config/samples/cfs-my-bucket-v3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cloudformation.linki.space/v1alpha1 2 | kind: Stack 3 | metadata: 4 | name: my-bucket 5 | spec: 6 | parameters: 7 | VersioningConfiguration: Enabled 8 | template: | 9 | --- 10 | AWSTemplateFormatVersion: '2010-09-09' 11 | 12 | Parameters: 13 | VersioningConfiguration: 14 | Type: String 15 | Default: Suspended 16 | AllowedValues: 17 | - Enabled 18 | - Suspended 19 | 20 | Resources: 21 | S3Bucket: 22 | Type: AWS::S3::Bucket 23 | Properties: 24 | VersioningConfiguration: 25 | Status: 26 | Ref: VersioningConfiguration 27 | -------------------------------------------------------------------------------- /config/samples/cfs-my-bucket-v4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cloudformation.linki.space/v1alpha1 2 | kind: Stack 3 | metadata: 4 | name: my-bucket 5 | spec: 6 | parameters: 7 | VersioningConfiguration: Enabled 8 | template: | 9 | --- 10 | AWSTemplateFormatVersion: '2010-09-09' 11 | 12 | Parameters: 13 | VersioningConfiguration: 14 | Type: String 15 | Default: Suspended 16 | AllowedValues: 17 | - Enabled 18 | - Suspended 19 | 20 | Resources: 21 | S3Bucket: 22 | Type: AWS::S3::Bucket 23 | Properties: 24 | VersioningConfiguration: 25 | Status: 26 | Ref: VersioningConfiguration 27 | 28 | Outputs: 29 | BucketName: 30 | Value: !Ref 'S3Bucket' 31 | Description: Name of the sample Amazon S3 bucket. 32 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - cfs-my-bucket-tags.yaml 4 | - cfs-my-bucket-v1.yaml 5 | - cfs-my-bucket-v2.yaml 6 | - cfs-my-bucket-v3.yaml 7 | - cfs-my-bucket-v4.yaml 8 | # +kubebuilder:scaffold:manifestskustomizesamples 9 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | # +kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.3.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.3.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.3.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.3.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.3.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.3.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /controllers/cloudformation_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Martin Linkhorst 5 | Copyright (c) 2021 Stephen Cuppett 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | package controllers 27 | 28 | import ( 29 | "context" 30 | coreerrors "errors" 31 | "github.com/aws/aws-sdk-go-v2/aws" 32 | "github.com/aws/aws-sdk-go-v2/service/cloudformation" 33 | cfTypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" 34 | cloudformationv1alpha1 "github.com/linki/cloudformation-operator/api/v1alpha1" 35 | "strings" 36 | ) 37 | 38 | var ( 39 | ErrStackNotFound = coreerrors.New("stack not found") 40 | ) 41 | 42 | type CloudFormationHelper struct { 43 | CloudFormation *cloudformation.Client 44 | } 45 | 46 | // Identify if the follower considers the state identified as terminal. 47 | func (cf *CloudFormationHelper) StackInTerminalState(status cfTypes.StackStatus) bool { 48 | statusString := string(status) 49 | if strings.HasSuffix(statusString, "_COMPLETE") { 50 | return true 51 | } 52 | if strings.HasSuffix(statusString, "_FAILED") { 53 | return true 54 | } 55 | return false 56 | } 57 | 58 | func (cf *CloudFormationHelper) GetStack(ctx context.Context, instance *cloudformationv1alpha1.Stack) (*cfTypes.Stack, error) { 59 | // Must use the stack ID to get details/finalization for deleted stacks 60 | name := instance.Status.StackID 61 | if name == "" { 62 | name = instance.Name 63 | } 64 | resp, err := cf.CloudFormation.DescribeStacks(ctx, &cloudformation.DescribeStacksInput{ 65 | NextToken: nil, 66 | StackName: aws.String(name), 67 | }) 68 | if err != nil { 69 | if strings.Contains(err.Error(), "does not exist") { 70 | return nil, ErrStackNotFound 71 | } 72 | return nil, err 73 | } 74 | if len(resp.Stacks) != 1 { 75 | return nil, ErrStackNotFound 76 | } 77 | 78 | return &resp.Stacks[0], nil 79 | } 80 | 81 | func (cf *CloudFormationHelper) GetStackResources(ctx context.Context, stackId string) ([]cloudformationv1alpha1.StackResource, error) { 82 | 83 | var next *string 84 | next = nil 85 | toReturn := make([]cloudformationv1alpha1.StackResource, 0) 86 | 87 | for { 88 | resp, err := cf.CloudFormation.ListStackResources(ctx, &cloudformation.ListStackResourcesInput{ 89 | NextToken: next, 90 | StackName: aws.String(stackId), 91 | }) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | for _, e := range resp.StackResourceSummaries { 97 | reason := "" 98 | if e.ResourceStatusReason != nil { 99 | reason = *e.ResourceStatusReason 100 | } 101 | physicalID := "" 102 | if e.PhysicalResourceId != nil { 103 | physicalID = *e.PhysicalResourceId 104 | } 105 | 106 | resourceSummary := cloudformationv1alpha1.StackResource{ 107 | LogicalId: *e.LogicalResourceId, 108 | PhysicalId: physicalID, 109 | Type: *e.ResourceType, 110 | Status: string(e.ResourceStatus), 111 | StatusReason: reason, 112 | } 113 | toReturn = append(toReturn, resourceSummary) 114 | } 115 | 116 | next = resp.NextToken 117 | if next == nil { 118 | break 119 | } 120 | } 121 | 122 | return toReturn, nil 123 | } 124 | -------------------------------------------------------------------------------- /controllers/stack_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Martin Linkhorst 5 | Copyright (c) 2021 Stephen Cuppett 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | package controllers 27 | 28 | import ( 29 | "context" 30 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 31 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 32 | "strings" 33 | 34 | "github.com/go-logr/logr" 35 | "k8s.io/apimachinery/pkg/api/errors" 36 | "k8s.io/apimachinery/pkg/runtime" 37 | ctrl "sigs.k8s.io/controller-runtime" 38 | "sigs.k8s.io/controller-runtime/pkg/client" 39 | 40 | "github.com/aws/aws-sdk-go-v2/aws" 41 | "github.com/aws/aws-sdk-go-v2/service/cloudformation" 42 | cfTypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" 43 | 44 | cloudformationv1alpha1 "github.com/linki/cloudformation-operator/api/v1alpha1" 45 | ) 46 | 47 | const ( 48 | controllerKey = "kubernetes.io/controlled-by" 49 | controllerValue = "cloudformation.linki.space/operator" 50 | legacyFinalizer = "finalizer.cloudformation.linki.space" 51 | stacksFinalizer = "cloudformation.linki.space/finalizer" 52 | ownerKey = "kubernetes.io/owned-by" 53 | ) 54 | 55 | // StackReconciler reconciles a Stack object 56 | type StackReconciler struct { 57 | client.Client 58 | Log logr.Logger 59 | Scheme *runtime.Scheme 60 | CloudFormation *cloudformation.Client 61 | StackFollower *StackFollower 62 | CloudFormationHelper *CloudFormationHelper 63 | DefaultTags map[string]string 64 | DefaultCapabilities []cfTypes.Capability 65 | DryRun bool 66 | } 67 | 68 | type StackLoop struct { 69 | ctx context.Context 70 | req ctrl.Request 71 | instance *cloudformationv1alpha1.Stack 72 | stack *cfTypes.Stack 73 | } 74 | 75 | // +kubebuilder:rbac:groups=cloudformation.linki.space,resources=stacks,verbs=get;list;watch;create;update;patch;delete 76 | // +kubebuilder:rbac:groups=cloudformation.linki.space,resources=stacks/status,verbs=get;update;patch 77 | // +kubebuilder:rbac:groups=cloudformation.linki.space,resources=stacks/finalizers,verbs=update 78 | 79 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 80 | // move the current state of the cluster closer to the desired state. 81 | // 82 | // For more details, check Reconcile and its Result here: 83 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile 84 | func (r *StackReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 85 | _ = r.Log.WithValues("Request.Namespace", req.Namespace, "Request.Name", req.Name) 86 | 87 | loop := &StackLoop{ctx, req, &cloudformationv1alpha1.Stack{}, nil} 88 | 89 | // Fetch the Stack instance 90 | err := r.Client.Get(loop.ctx, loop.req.NamespacedName, loop.instance) 91 | if err != nil { 92 | if errors.IsNotFound(err) { 93 | // Request object not found, could have been deleted after reconcile request. 94 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 95 | // Return and don't requeue 96 | r.Log.Info("Stack resource not found. Ignoring since object must be deleted") 97 | return ctrl.Result{}, nil 98 | } 99 | // Error reading the object - requeue the request. 100 | r.Log.Error(err, "Failed to get Stack") 101 | return ctrl.Result{}, err 102 | } 103 | 104 | // Check if the Stack instance is marked to be deleted, which is 105 | // indicated by the deletion timestamp being set. 106 | isStackMarkedToBeDeleted := loop.instance.GetDeletionTimestamp() != nil 107 | if isStackMarkedToBeDeleted { 108 | if controllerutil.ContainsFinalizer(loop.instance, stacksFinalizer) || 109 | controllerutil.ContainsFinalizer(loop.instance, legacyFinalizer) { 110 | // Remove stacksFinalizer. Once all finalizers have been 111 | // removed, the object will be deleted. 112 | if loop.instance.Status.StackStatus == "DELETE_COMPLETE" { 113 | controllerutil.RemoveFinalizer(loop.instance, stacksFinalizer) 114 | controllerutil.RemoveFinalizer(loop.instance, legacyFinalizer) 115 | err := r.Update(loop.ctx, loop.instance) 116 | if err != nil { 117 | r.Log.Error(err, "Failed to update stack to drop finalizer") 118 | return ctrl.Result{}, err 119 | } 120 | r.Log.Info("Successfully finalized stack") 121 | } else { 122 | // Run finalization logic for stacksFinalizer. If the 123 | // finalization logic fails, don't remove the finalizer so 124 | // that we can retry during the next reconciliation. 125 | err := r.deleteStack(loop) 126 | if err != nil { 127 | r.Log.Error(err, "Failed to delete stack") 128 | return ctrl.Result{}, err 129 | } 130 | } 131 | } 132 | return ctrl.Result{}, nil 133 | } 134 | 135 | // Add finalizer for this CR 136 | if !controllerutil.ContainsFinalizer(loop.instance, stacksFinalizer) { 137 | controllerutil.AddFinalizer(loop.instance, stacksFinalizer) 138 | err = r.Update(ctx, loop.instance) 139 | return ctrl.Result{}, err 140 | } 141 | 142 | exists, err := r.stackExists(loop) 143 | if err != nil { 144 | return reconcile.Result{}, err 145 | } 146 | 147 | if exists { 148 | // If the stack is in progress but not being followed, follow it to catch updates 149 | // If it is being followed, we want the same thing, just send it over to the other thread to check it in all 150 | // IN_PROGRESS cases. 151 | if !r.CloudFormationHelper.StackInTerminalState(loop.stack.StackStatus) { 152 | r.StackFollower.SubmissionChannel <- loop.instance 153 | return ctrl.Result{}, nil 154 | } 155 | 156 | return reconcile.Result{}, r.updateStack(loop) 157 | } 158 | 159 | return ctrl.Result{}, r.createStack(loop) 160 | } 161 | 162 | func (r *StackReconciler) createStack(loop *StackLoop) error { 163 | r.Log.WithValues("stack", loop.instance.Name).Info("creating stack") 164 | 165 | if r.DryRun { 166 | r.Log.WithValues("stack", loop.instance.Name).Info("skipping stack creation") 167 | return nil 168 | } 169 | 170 | hasOwnership, err := r.hasOwnership(loop) 171 | if err != nil { 172 | return err 173 | } 174 | 175 | if !hasOwnership { 176 | r.Log.WithValues("stack", loop.instance.Name).Info("no ownership") 177 | return nil 178 | } 179 | 180 | stackTags, err := r.stackTags(loop) 181 | if err != nil { 182 | r.Log.WithValues("stack", loop.instance.Name).Error(err, "error compiling tags") 183 | return err 184 | } 185 | 186 | input := &cloudformation.CreateStackInput{ 187 | Capabilities: r.DefaultCapabilities, 188 | StackName: aws.String(loop.instance.Name), 189 | TemplateBody: aws.String(loop.instance.Spec.Template), 190 | Parameters: r.stackParameters(loop), 191 | Tags: stackTags, 192 | } 193 | 194 | output, err := r.CloudFormation.CreateStack(loop.ctx, input) 195 | if err != nil { 196 | return err 197 | } 198 | loop.instance.Status.StackID = *output.StackId 199 | 200 | r.StackFollower.SubmissionChannel <- loop.instance 201 | return nil 202 | } 203 | 204 | func (r *StackReconciler) updateStack(loop *StackLoop) error { 205 | r.Log.WithValues("stack", loop.instance.Name).Info("updating stack") 206 | 207 | if r.DryRun { 208 | r.Log.WithValues("stack", loop.instance.Name).Info("skipping stack update") 209 | return nil 210 | } 211 | 212 | hasOwnership, err := r.hasOwnership(loop) 213 | if err != nil { 214 | return err 215 | } 216 | 217 | if !hasOwnership { 218 | r.Log.WithValues("stack", loop.instance.Name).Info("no ownership") 219 | return nil 220 | } 221 | 222 | stackTags, err := r.stackTags(loop) 223 | if err != nil { 224 | r.Log.WithValues("stack", loop.instance.Name).Error(err, "error compiling tags") 225 | return err 226 | } 227 | 228 | input := &cloudformation.UpdateStackInput{ 229 | Capabilities: r.DefaultCapabilities, 230 | StackName: aws.String(loop.instance.Name), 231 | TemplateBody: aws.String(loop.instance.Spec.Template), 232 | Parameters: r.stackParameters(loop), 233 | Tags: stackTags, 234 | } 235 | 236 | if _, err := r.CloudFormation.UpdateStack(loop.ctx, input); err != nil { 237 | if strings.Contains(err.Error(), "No updates are to be performed.") { 238 | r.Log.WithValues("stack", loop.instance.Name).Info("stack already updated") 239 | return nil 240 | } 241 | return err 242 | } 243 | 244 | r.StackFollower.SubmissionChannel <- loop.instance 245 | return nil 246 | } 247 | 248 | func (r *StackReconciler) deleteStack(loop *StackLoop) error { 249 | r.Log.WithValues("stack", loop.instance.Name).Info("deleting stack") 250 | 251 | if r.DryRun { 252 | r.Log.WithValues("stack", loop.instance.Name).Info("skipping stack deletion") 253 | return nil 254 | } 255 | 256 | hasOwnership, err := r.hasOwnership(loop) 257 | if err != nil { 258 | return err 259 | } 260 | 261 | if !hasOwnership { 262 | r.Log.WithValues("stack", loop.instance.Name).Info("no ownership") 263 | return nil 264 | } 265 | 266 | input := &cloudformation.DeleteStackInput{ 267 | StackName: aws.String(loop.instance.Name), 268 | } 269 | 270 | if _, err := r.CloudFormation.DeleteStack(loop.ctx, input); err != nil { 271 | return err 272 | } 273 | 274 | r.StackFollower.SubmissionChannel <- loop.instance 275 | return nil 276 | } 277 | 278 | func (r *StackReconciler) getStack(loop *StackLoop, noCache bool) (*cfTypes.Stack, error) { 279 | 280 | var err error 281 | 282 | if noCache || loop.stack == nil { 283 | // Must use the stack ID to get details/finalization for deleted stacks 284 | loop.stack, err = r.CloudFormationHelper.GetStack(loop.ctx, loop.instance) 285 | if err != nil { 286 | if strings.Contains(err.Error(), "does not exist") { 287 | return nil, ErrStackNotFound 288 | } 289 | return nil, err 290 | } 291 | } 292 | 293 | return loop.stack, nil 294 | } 295 | 296 | func (r *StackReconciler) stackExists(loop *StackLoop) (bool, error) { 297 | _, err := r.getStack(loop, false) 298 | if err != nil { 299 | if err == ErrStackNotFound { 300 | return false, nil 301 | } 302 | return false, err 303 | } 304 | 305 | return true, nil 306 | } 307 | 308 | func (r *StackReconciler) hasOwnership(loop *StackLoop) (bool, error) { 309 | exists, err := r.stackExists(loop) 310 | if err != nil { 311 | return false, err 312 | } 313 | if !exists { 314 | return true, nil 315 | } 316 | 317 | cfs, err := r.getStack(loop, false) 318 | if err != nil { 319 | return false, err 320 | } 321 | 322 | for _, tag := range cfs.Tags { 323 | if *tag.Key == controllerKey && *tag.Value == controllerValue { 324 | return true, nil 325 | } 326 | } 327 | 328 | return false, nil 329 | } 330 | 331 | // stackParameters converts the parameters field on a Stack resource to CloudFormation Parameters. 332 | func (r *StackReconciler) stackParameters(loop *StackLoop) []cfTypes.Parameter { 333 | var params []cfTypes.Parameter 334 | if loop.instance.Spec.Parameters != nil { 335 | for k, v := range loop.instance.Spec.Parameters { 336 | params = append(params, cfTypes.Parameter{ 337 | ParameterKey: aws.String(k), 338 | ParameterValue: aws.String(v), 339 | }) 340 | } 341 | } 342 | return params 343 | } 344 | 345 | // stackTags converts the tags field on a Stack resource to CloudFormation Tags. 346 | // Furthermore, it adds a tag for marking ownership as well as any tags given by defaultTags. 347 | func (r *StackReconciler) stackTags(loop *StackLoop) ([]cfTypes.Tag, error) { 348 | // ownership tags 349 | tags := []cfTypes.Tag{ 350 | { 351 | Key: aws.String(controllerKey), 352 | Value: aws.String(controllerValue), 353 | }, 354 | { 355 | Key: aws.String(ownerKey), 356 | Value: aws.String(string(loop.instance.UID)), 357 | }, 358 | } 359 | 360 | // default tags 361 | for k, v := range r.DefaultTags { 362 | tags = append(tags, cfTypes.Tag{ 363 | Key: aws.String(k), 364 | Value: aws.String(v), 365 | }) 366 | } 367 | 368 | // tags specified on the Stack resource 369 | if loop.instance.Spec.Tags != nil { 370 | for k, v := range loop.instance.Spec.Tags { 371 | tags = append(tags, cfTypes.Tag{ 372 | Key: aws.String(k), 373 | Value: aws.String(v), 374 | }) 375 | } 376 | } 377 | 378 | return tags, nil 379 | } 380 | 381 | // SetupWithManager sets up the controller with the Manager. 382 | func (r *StackReconciler) SetupWithManager(mgr ctrl.Manager) error { 383 | return ctrl.NewControllerManagedBy(mgr). 384 | For(&cloudformationv1alpha1.Stack{}). 385 | Complete(r) 386 | } 387 | -------------------------------------------------------------------------------- /controllers/stack_follower.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Martin Linkhorst 5 | Copyright (c) 2021 Stephen Cuppett 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | package controllers 27 | 28 | import ( 29 | "context" 30 | cfTypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" 31 | "github.com/go-logr/logr" 32 | cloudformationv1alpha1 "github.com/linki/cloudformation-operator/api/v1alpha1" 33 | "k8s.io/apimachinery/pkg/api/errors" 34 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 | "reflect" 36 | "sigs.k8s.io/controller-runtime/pkg/client" 37 | "sync" 38 | "time" 39 | ) 40 | 41 | // StackFollower ensures a Stack object is monitored until it reaches a terminal state 42 | type StackFollower struct { 43 | client.Client 44 | Log logr.Logger 45 | CloudFormationHelper *CloudFormationHelper 46 | SubmissionChannel chan *cloudformationv1alpha1.Stack 47 | // StackID -> Kube Stack object 48 | mapPollingList sync.Map 49 | } 50 | 51 | func (f *StackFollower) Receiver() { 52 | 53 | for { 54 | toBeFollowed := <-f.SubmissionChannel 55 | f.Log.Info("Received follow request", "UID", toBeFollowed.UID, "Stack ID", toBeFollowed.Status.StackID) 56 | if !f.BeingFollowed(toBeFollowed.Status.StackID) { 57 | f.startFollowing(toBeFollowed) 58 | } 59 | _ = f.UpdateStackStatus(context.TODO(), toBeFollowed) 60 | } 61 | } 62 | 63 | // Identify if the follower is actively working this one. 64 | func (f *StackFollower) BeingFollowed(stackId string) bool { 65 | _, followed := f.mapPollingList.Load(stackId) 66 | f.Log.Info("Following Stack", "StackID", stackId, "Following", followed) 67 | return followed 68 | } 69 | 70 | // Identify if the follower is actively working this one. 71 | func (f *StackFollower) startFollowing(stack *cloudformationv1alpha1.Stack) { 72 | f.mapPollingList.Store(stack.Status.StackID, stack) 73 | f.Log.Info("Now following Stack", "StackID", stack.Status.StackID) 74 | } 75 | 76 | // Identify if the follower is actively working this one. 77 | func (f *StackFollower) stopFollowing(stackId string) { 78 | f.mapPollingList.Delete(stackId) 79 | f.Log.Info("Stopped following Stack", "StackID", stackId) 80 | } 81 | 82 | // Allow passing a current/recent fetch of the stack object to the method (optionally) 83 | func (f *StackFollower) UpdateStackStatus(ctx context.Context, instance *cloudformationv1alpha1.Stack, stack ...*cfTypes.Stack) error { 84 | var err error 85 | var cfs *cfTypes.Stack 86 | update := false 87 | 88 | if len(stack) > 0 { 89 | cfs = stack[0] 90 | } 91 | if cfs == nil { 92 | cfs, err = f.CloudFormationHelper.GetStack(ctx, instance) 93 | if err != nil { 94 | f.Log.Error(err, "Failed to get CloudFormation stack") 95 | return err 96 | } 97 | } 98 | 99 | outputs := map[string]string{} 100 | if cfs.Outputs != nil && len(cfs.Outputs) > 0 { 101 | for _, output := range cfs.Outputs { 102 | outputs[*output.OutputKey] = *output.OutputValue 103 | } 104 | } 105 | 106 | // Checking the status 107 | if string(cfs.StackStatus) != instance.Status.StackStatus { 108 | update = true 109 | instance.Status.StackStatus = string(cfs.StackStatus) 110 | instance.Status.CreatedTime = metav1.NewTime(*cfs.CreationTime) 111 | if cfs.LastUpdatedTime != nil { 112 | instance.Status.UpdatedTime = metav1.NewTime(*cfs.LastUpdatedTime) 113 | } 114 | } 115 | 116 | // Checking stack ID and outputs for changes. 117 | stackID := *cfs.StackId 118 | if stackID != instance.Status.StackID || !reflect.DeepEqual(outputs, instance.Status.Outputs) { 119 | update = true 120 | instance.Status.StackID = stackID 121 | if len(outputs) > 0 { 122 | instance.Status.Outputs = outputs 123 | } 124 | } 125 | 126 | // Recording all stack resources 127 | resources, err := f.CloudFormationHelper.GetStackResources(ctx, instance.Status.StackID) 128 | if err != nil { 129 | f.Log.Error(err, "Failed to get Stack Resources") 130 | return err 131 | } 132 | if !reflect.DeepEqual(resources, instance.Status.Resources) { 133 | update = true 134 | instance.Status.Resources = resources 135 | } 136 | 137 | if update { 138 | err = f.Status().Update(ctx, instance) 139 | if err != nil { 140 | f.Log.Error(err, "Failed to update Stack Status") 141 | if errors.IsNotFound(err) { 142 | // Request object not found, could have been deleted after reconcile request. 143 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 144 | // Return and don't requeue 145 | // return reconcile.Result{}, nil 146 | return nil 147 | } 148 | // Error reading the object - requeue the request. 149 | // return reconcile.Result{}, err 150 | return err 151 | } 152 | } 153 | 154 | return nil 155 | } 156 | 157 | func (f *StackFollower) processStack(key interface{}, value interface{}) bool { 158 | 159 | stackId := key.(string) 160 | stack := value.(*cloudformationv1alpha1.Stack) 161 | 162 | cfs, err := f.CloudFormationHelper.GetStack(context.TODO(), stack) 163 | if err != nil { 164 | if err == ErrStackNotFound { 165 | f.Log.Error(err, "Stack Not Found", "UID", stack.UID, "Stack ID", stackId) 166 | f.stopFollowing(stackId) 167 | } else { 168 | f.Log.Error(err, "Error retrieving stack for processing", "UID", stack.UID, "Stack ID", stackId) 169 | } 170 | } else { 171 | // Have to remove the lock on the last pass, so the reconciler can catch it on the next loop. 172 | if f.CloudFormationHelper.StackInTerminalState(cfs.StackStatus) { 173 | f.stopFollowing(stackId) 174 | } 175 | err = f.UpdateStackStatus(context.TODO(), stack, cfs) 176 | if err != nil { 177 | f.Log.Error(err, "Failed to update stack status", "UID", stack.UID, "Stack ID", stackId) 178 | // On error put it back to make sure we save it next time. 179 | f.startFollowing(stack) 180 | } 181 | } 182 | 183 | return true 184 | } 185 | 186 | func (f *StackFollower) Worker() { 187 | 188 | for { 189 | time.Sleep(time.Second * 5) 190 | f.mapPollingList.Range(f.processStack) 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Martin Linkhorst 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package controllers 26 | 27 | import ( 28 | "path/filepath" 29 | "testing" 30 | 31 | . "github.com/onsi/ginkgo" 32 | . "github.com/onsi/gomega" 33 | "k8s.io/client-go/kubernetes/scheme" 34 | "k8s.io/client-go/rest" 35 | "sigs.k8s.io/controller-runtime/pkg/client" 36 | "sigs.k8s.io/controller-runtime/pkg/envtest" 37 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 38 | logf "sigs.k8s.io/controller-runtime/pkg/log" 39 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 40 | 41 | cloudformationlinkispacev1alpha1 "github.com/linki/cloudformation-operator/api/v1alpha1" 42 | // +kubebuilder:scaffold:imports 43 | ) 44 | 45 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 46 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 47 | 48 | var cfg *rest.Config 49 | var k8sClient client.Client 50 | var testEnv *envtest.Environment 51 | 52 | func TestAPIs(t *testing.T) { 53 | RegisterFailHandler(Fail) 54 | 55 | RunSpecsWithDefaultAndCustomReporters(t, 56 | "Controller Suite", 57 | []Reporter{printer.NewlineReporter{}}) 58 | } 59 | 60 | var _ = BeforeSuite(func() { 61 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 62 | 63 | By("bootstrapping test environment") 64 | testEnv = &envtest.Environment{ 65 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 66 | } 67 | 68 | cfg, err := testEnv.Start() 69 | Expect(err).NotTo(HaveOccurred()) 70 | Expect(cfg).NotTo(BeNil()) 71 | 72 | err = cloudformationlinkispacev1alpha1.AddToScheme(scheme.Scheme) 73 | Expect(err).NotTo(HaveOccurred()) 74 | 75 | // +kubebuilder:scaffold:scheme 76 | 77 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 78 | Expect(err).NotTo(HaveOccurred()) 79 | Expect(k8sClient).NotTo(BeNil()) 80 | 81 | }, 60) 82 | 83 | var _ = AfterSuite(func() { 84 | By("tearing down the test environment") 85 | err := testEnv.Stop() 86 | Expect(err).NotTo(HaveOccurred()) 87 | }) 88 | -------------------------------------------------------------------------------- /docs/img/stack-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linki/cloudformation-operator/f8e5aef4187ea653916fb95407aab19c33433338/docs/img/stack-create.png -------------------------------------------------------------------------------- /docs/img/stack-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linki/cloudformation-operator/f8e5aef4187ea653916fb95407aab19c33433338/docs/img/stack-delete.png -------------------------------------------------------------------------------- /docs/img/stack-tags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linki/cloudformation-operator/f8e5aef4187ea653916fb95407aab19c33433338/docs/img/stack-tags.png -------------------------------------------------------------------------------- /docs/img/stack-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linki/cloudformation-operator/f8e5aef4187ea653916fb95407aab19c33433338/docs/img/stack-update.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/linki/cloudformation-operator 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go-v2 v1.3.0 7 | github.com/aws/aws-sdk-go-v2/config v1.1.3 8 | github.com/aws/aws-sdk-go-v2/credentials v1.1.3 9 | github.com/aws/aws-sdk-go-v2/service/cloudformation v1.2.0 10 | github.com/aws/aws-sdk-go-v2/service/sts v1.2.0 11 | github.com/go-logr/logr v0.4.0 12 | github.com/onsi/ginkgo v1.15.2 13 | github.com/onsi/gomega v1.11.0 14 | github.com/spf13/pflag v1.0.5 15 | k8s.io/apimachinery v0.20.5 16 | k8s.io/client-go v0.20.5 17 | sigs.k8s.io/controller-runtime v0.8.3 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 14 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 15 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 16 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 17 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 18 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 19 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 20 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 21 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 22 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 23 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 24 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 25 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 26 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 27 | github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= 28 | github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 29 | github.com/Azure/go-autorest/autorest v0.11.1 h1:eVvIXUKiTgv++6YnWb42DUA1YL7qDugnKP0HljexdnQ= 30 | github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= 31 | github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= 32 | github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= 33 | github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= 34 | github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= 35 | github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= 36 | github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= 37 | github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= 38 | github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= 39 | github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= 40 | github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= 41 | github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= 42 | github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 43 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 44 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 45 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 46 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 47 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 48 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 49 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 50 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 51 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 52 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 53 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 54 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 55 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 56 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 57 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 58 | github.com/aws/aws-sdk-go-v2 v1.3.0 h1:2B/SbB1oOJe8RSl/TIgE11BDE4sX7Z+JupLxTdA2Rjs= 59 | github.com/aws/aws-sdk-go-v2 v1.3.0/go.mod h1:hTQc/9pYq5bfFACIUY9tc/2SYWd9Vnmw+testmuQeRY= 60 | github.com/aws/aws-sdk-go-v2/config v1.1.3 h1:pYDr4DTr0w4GfweXhX2ns1ZGyH46nLP/ZeQQodl1s68= 61 | github.com/aws/aws-sdk-go-v2/config v1.1.3/go.mod h1:yf3tNRNqZKlylefSdp5R3v+sm1el90fhUTcSa/t69Ro= 62 | github.com/aws/aws-sdk-go-v2/credentials v1.1.3 h1:Q0S5OPP4l9kWrmPNK500pdQhg81x4E3UpvugYG5Wilc= 63 | github.com/aws/aws-sdk-go-v2/credentials v1.1.3/go.mod h1:afuzRuLhPEe08fePFh4gI9jnHuXd8AJDCYZNo3rKRKE= 64 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.4 h1:V7DbyJMo5kq31ZiyQMmjihjexftM1oJ6luRs09M5/Uc= 65 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.4/go.mod h1:BDw1ukadBHn//M/n7LqpEgimGS0QtiJePnygMsbuYMs= 66 | github.com/aws/aws-sdk-go-v2/service/cloudformation v1.2.0 h1:XREWXNvpP/7K8++qSLdDM6+nkN9oV132XK15NfSGVtA= 67 | github.com/aws/aws-sdk-go-v2/service/cloudformation v1.2.0/go.mod h1:ikgX60u5cVupsWa5gQHL82MhalCFX6mzU8gHNNvzEeE= 68 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.4 h1:DRIpujxvhdv3+xLXCoaKk1VB4vk/Sh8sIOBewLJJpes= 69 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.4/go.mod h1:DGOKKGeqXdIWX3xD5DKr4otrgNw5cstwUCJYwSKxbp0= 70 | github.com/aws/aws-sdk-go-v2/service/sso v1.1.3 h1:NVLHdz3KtZhCrX0GWZKpdINKuDh7PsaZ8Vsr4OxP88s= 71 | github.com/aws/aws-sdk-go-v2/service/sso v1.1.3/go.mod h1:F1l5lKzDzoY3/0cFbB3AA/ey9MsNiH5rhf6HOssy1/Q= 72 | github.com/aws/aws-sdk-go-v2/service/sts v1.2.0 h1:fGo3atNqTj3SOu1VKb52BUzRcYOhrpJ1wHrzTuMs+QA= 73 | github.com/aws/aws-sdk-go-v2/service/sts v1.2.0/go.mod h1:iGyHChDhzbddWEbC/+g/mT3z+A2JTJthcw+8QubXSgk= 74 | github.com/aws/smithy-go v1.2.0 h1:0PoGBWXkXDIyVdPaZW9gMhaGzj3UOAgTdiVoHuuZAFA= 75 | github.com/aws/smithy-go v1.2.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= 76 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 77 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 78 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 79 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 80 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 81 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 82 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 83 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 84 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 85 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 86 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 87 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 88 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 89 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 90 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 91 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 92 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= 93 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 94 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 95 | github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 96 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 97 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 98 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 99 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 100 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 101 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 102 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 103 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 104 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 105 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 106 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 107 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 108 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 109 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 110 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 111 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 112 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 113 | github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 114 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 115 | github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 116 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 117 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 118 | github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 119 | github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= 120 | github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 121 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 122 | github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= 123 | github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= 124 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 125 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 126 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 127 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 128 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 129 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 130 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 131 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 132 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 133 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 134 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 135 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 136 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 137 | github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= 138 | github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= 139 | github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= 140 | github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= 141 | github.com/go-logr/zapr v0.2.0 h1:v6Ji8yBW77pva6NkJKQdHLAJKrIJKRHz0RXwPqCHSR4= 142 | github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= 143 | github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 144 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 145 | github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= 146 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 147 | github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= 148 | github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 149 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 150 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 151 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 152 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 153 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 154 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 155 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 156 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 157 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 158 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 159 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 160 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= 161 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 162 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 163 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 164 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 165 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 166 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 167 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 168 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 169 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 170 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 171 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 172 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 173 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 174 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 175 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 176 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 177 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 178 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 179 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= 180 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 181 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 182 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 183 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 184 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 185 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 186 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 187 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 188 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 189 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= 190 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 191 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 192 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= 193 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 194 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 195 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 196 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 197 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 198 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 199 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 200 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 201 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 202 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 203 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 204 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 205 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 206 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 207 | github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= 208 | github.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM= 209 | github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= 210 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 211 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 212 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 213 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 214 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 215 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 216 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 217 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 218 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 219 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 220 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 221 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 222 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 223 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 224 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 225 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 226 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 227 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 228 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 229 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 230 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 231 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 232 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 233 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 234 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 235 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 236 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 237 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 238 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 239 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 240 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 241 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 242 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 243 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 244 | github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc= 245 | github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 246 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 247 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 248 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 249 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 250 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 251 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 252 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 253 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 254 | github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= 255 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 256 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 257 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 258 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 259 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 260 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 261 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 262 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 263 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 264 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 265 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 266 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 267 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 268 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 269 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 270 | github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 271 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 272 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 273 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 274 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 275 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 276 | github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= 277 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 278 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 279 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 280 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 281 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 282 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= 283 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 284 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 285 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 286 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 287 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 288 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 289 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 290 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 291 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 292 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 293 | github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= 294 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 295 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 296 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 297 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 298 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 299 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 300 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 301 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 302 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 303 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 304 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 305 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 306 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 307 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 308 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 309 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 310 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 311 | github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 312 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 313 | github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 314 | github.com/onsi/ginkgo v1.15.2 h1:l77YT15o814C2qVL47NOyjV/6RbaP7kKdrvZnxQ3Org= 315 | github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o= 316 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 317 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 318 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 319 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 320 | github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 321 | github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= 322 | github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= 323 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 324 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 325 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 326 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 327 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 328 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 329 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 330 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 331 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 332 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 333 | github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= 334 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 335 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 336 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 337 | github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= 338 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 339 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 340 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 341 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 342 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 343 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 344 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 345 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 346 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 347 | github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= 348 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 349 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 350 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 351 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 352 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 353 | github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= 354 | github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 355 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 356 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 357 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 358 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 359 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 360 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 361 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 362 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 363 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 364 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 365 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 366 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 367 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 368 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 369 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 370 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 371 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 372 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 373 | github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= 374 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 375 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 376 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 377 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 378 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 379 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 380 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 381 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 382 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 383 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 384 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 385 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 386 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 387 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 388 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 389 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 390 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 391 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 392 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 393 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 394 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 395 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 396 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 397 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 398 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 399 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 400 | go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= 401 | go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= 402 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 403 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 404 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 405 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 406 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 407 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 408 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= 409 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 410 | go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= 411 | go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 412 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 413 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= 414 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 415 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= 416 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 417 | go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 418 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 419 | go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= 420 | go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= 421 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 422 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 423 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 424 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 425 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 426 | golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 427 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 428 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 429 | golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= 430 | golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 431 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 432 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 433 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 434 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 435 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 436 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 437 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 438 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 439 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 440 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 441 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 442 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 443 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 444 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 445 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 446 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 447 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 448 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 449 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 450 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 451 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 452 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= 453 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 454 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 455 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 456 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 457 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 458 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 459 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 460 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 461 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 462 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 463 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 464 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 465 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 466 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 467 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 468 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 469 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 470 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 471 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 472 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 473 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 474 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 475 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 476 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 477 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 478 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 479 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 480 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 481 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 482 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 483 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 484 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 485 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 486 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 487 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 488 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 489 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 490 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 491 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 492 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= 493 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 494 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 495 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 496 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 497 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 498 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= 499 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 500 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 501 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 502 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 503 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 504 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 505 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 506 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 507 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 508 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 509 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 510 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 511 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 512 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 513 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 514 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 515 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 516 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 517 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 518 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 519 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 520 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 521 | golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 522 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 523 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 524 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 525 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 526 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 527 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 528 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 529 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 530 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 531 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 532 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 533 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 534 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 535 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 536 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 537 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 538 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 539 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 540 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 541 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 542 | golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 543 | golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= 544 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 545 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 546 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 547 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 548 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 549 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 550 | golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= 551 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 552 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 553 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 554 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 555 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 556 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= 557 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 558 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 559 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 560 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 561 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 562 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 563 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 564 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 565 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 566 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 567 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 568 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 569 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 570 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 571 | golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 572 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 573 | golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 574 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 575 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 576 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 577 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 578 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 579 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 580 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 581 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 582 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 583 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 584 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 585 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 586 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 587 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 588 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 589 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 590 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 591 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 592 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 593 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 594 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 595 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 596 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 597 | golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 598 | golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 599 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= 600 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 601 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 602 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 603 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 604 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 605 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 606 | gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k= 607 | gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= 608 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 609 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 610 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 611 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 612 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 613 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 614 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 615 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 616 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 617 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 618 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 619 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 620 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 621 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 622 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 623 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= 624 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 625 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 626 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 627 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 628 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 629 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 630 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 631 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 632 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 633 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 634 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 635 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 636 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 637 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 638 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 639 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 640 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 641 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 642 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 643 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 644 | google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 645 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 646 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 647 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 648 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 649 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 650 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 651 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 652 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 653 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 654 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 655 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 656 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 657 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 658 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 659 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 660 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 661 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= 662 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 663 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 664 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 665 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 666 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 667 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 668 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 669 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 670 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 671 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 672 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 673 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 674 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 675 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 676 | gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 677 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 678 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 679 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 680 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 681 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 682 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 683 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 684 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 685 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 686 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 687 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 688 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 689 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= 690 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 691 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 692 | gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= 693 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 694 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 695 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 696 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 697 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 698 | honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= 699 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 700 | k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= 701 | k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8= 702 | k8s.io/api v0.20.5 h1:zsMTffV0Le2EiI0aKvlTHEnXGxk1HiqGRhJcCPiI7JI= 703 | k8s.io/api v0.20.5/go.mod h1:FQjAceXnVaWDeov2YUWhOb6Yt+5UjErkp6UO3nczO1Y= 704 | k8s.io/apiextensions-apiserver v0.20.1 h1:ZrXQeslal+6zKM/HjDXLzThlz/vPSxrfK3OqL8txgVQ= 705 | k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk= 706 | k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= 707 | k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= 708 | k8s.io/apimachinery v0.20.5 h1:wO/FxMVRn223rAKxnBbwCyuN96bS9MFTIvP0e/V7cps= 709 | k8s.io/apimachinery v0.20.5/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= 710 | k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= 711 | k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= 712 | k8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE= 713 | k8s.io/client-go v0.20.5 h1:dJGtYUvFrFGjQ+GjXEIby0gZWdlAOc0xJBJqY3VyDxA= 714 | k8s.io/client-go v0.20.5/go.mod h1:Ee5OOMMYvlH8FCZhDsacjMlCBwetbGZETwo1OA+e6Zw= 715 | k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= 716 | k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= 717 | k8s.io/component-base v0.20.2 h1:LMmu5I0pLtwjpp5009KLuMGFqSc2S2isGw8t1hpYKLE= 718 | k8s.io/component-base v0.20.2/go.mod h1:pzFtCiwe/ASD0iV7ySMu8SYVJjCapNM9bjvk7ptpKh0= 719 | k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 720 | k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= 721 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= 722 | k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= 723 | k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ= 724 | k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= 725 | k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c= 726 | k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= 727 | k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 728 | k8s.io/utils v0.0.0-20210111153108-fddb29f9d009 h1:0T5IaWHO3sJTEmCP6mUlBvMukxPKUQWqiI/YuiBNMiQ= 729 | k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 730 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 731 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 732 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 733 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= 734 | sigs.k8s.io/controller-runtime v0.8.3 h1:GMHvzjTmaWHQB8HadW+dIvBoJuLvZObYJ5YoZruPRao= 735 | sigs.k8s.io/controller-runtime v0.8.3/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU= 736 | sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8= 737 | sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= 738 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 739 | sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= 740 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 741 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Martin Linkhorst 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ -------------------------------------------------------------------------------- /helm/README.md: -------------------------------------------------------------------------------- 1 | # cloudformation-operator 2 | 3 | This is the Helm chart for the [cloudformation-operator](https://github.com/linki/cloudformation-operator) 4 | 5 | ## Prerequisites 6 | 7 | - Kubernetes 1.17+ 8 | 9 | ## Installing the chart 10 | Create AWS resources with Kubernetes 11 | The chart can be installed by running: 12 | 13 | ```bash 14 | $ helm install cloudformation-operator-system helm/cloudformation-operator 15 | ``` 16 | 17 | ## Configuration 18 | 19 | The following table lists the configurable parameters of the cloudformation-operator chart and their default values. 20 | 21 | | Parameter | Description | Default | 22 | | ------------------------- | -------------------------------------- | -------------------------------------------------- | 23 | | `operator.region` | The AWS_REGION to set | `eu-central-1` | 24 | | `image.repository` | Container image repository | `quay.io/linki/cloudformation-operator` | 25 | | `image.tag` | Container image tag | | 26 | | `image.sha` | Container image shasum (overrides tag) | | 27 | | `image.pullPolicy` | Container pull policy | `IfNotPresent` | 28 | | `affinity` | affinity settings for pod assignment | `{}` | 29 | | `extraEnv` | Optional environment variables | `[]` | 30 | | `extraVolumes` | Custom Volumes | `[]` | 31 | | `extraVolumeMounts` | Custom VolumeMounts | `[]` | 32 | | `nodeSelector` | Node labels for pod assignment | `{}` | 33 | | `podAnnotations` | Annotations to attach to pod | `{}` | 34 | | `rbac.create` | Create RBAC roles | `true` | 35 | | `serviceAccount.name` | Existing ServiceAccount to use | `cloudformation-operator` | | 36 | | `replicas` | Deployment replicas | `1` | 37 | | `resources` | container resource requests and limits | `{}` | 38 | | `tolerations` | Toleration labels for pod assignment | `[]` | 39 | | `tags` | You may want to assign tags to your CloudFormation stacks | `[]` | 40 | | `capability.enabled` | Enable specified capabilities for all stacks managed by the operator instance | `[]` | -------------------------------------------------------------------------------- /helm/cloudformation-operator/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /helm/cloudformation-operator/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | appVersion: "v0.9.0" 3 | description: A Helm chart for Kubernetes operator for managing CloudFormation stacks 4 | name: cloudformation-operator 5 | version: 0.2.0 6 | type: application 7 | home: https://github.com/linki/cloudformation-operator 8 | keywords: 9 | - AWS 10 | sources: 11 | - https://github.com/linki/cloudformation-operator 12 | maintainers: 13 | - name: mbalazs90 14 | email: xpbazsi@gmail.com 15 | - name: cuppett 16 | email: steve@cuppett.com 17 | engine: gotpl 18 | -------------------------------------------------------------------------------- /helm/cloudformation-operator/crds/stack.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.4.1 6 | creationTimestamp: null 7 | name: stacks.cloudformation.linki.space 8 | spec: 9 | group: cloudformation.linki.space 10 | names: 11 | kind: Stack 12 | listKind: StackList 13 | plural: stacks 14 | singular: stack 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: Stack is the Schema for the stacks API 21 | properties: 22 | apiVersion: 23 | 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' 24 | type: string 25 | kind: 26 | 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' 27 | type: string 28 | metadata: 29 | type: object 30 | spec: 31 | description: Defines the desired state of Stack 32 | properties: 33 | parameters: 34 | additionalProperties: 35 | type: string 36 | type: object 37 | tags: 38 | additionalProperties: 39 | type: string 40 | type: object 41 | template: 42 | type: string 43 | required: 44 | - template 45 | type: object 46 | status: 47 | description: Defines the observed state of Stack 48 | properties: 49 | createdTime: 50 | format: date-time 51 | nullable: true 52 | type: string 53 | outputs: 54 | additionalProperties: 55 | type: string 56 | nullable: true 57 | type: object 58 | resources: 59 | items: 60 | description: Defines a resource provided/managed by a Stack and its current state 61 | properties: 62 | logicalID: 63 | type: string 64 | physicalID: 65 | type: string 66 | status: 67 | type: string 68 | statusReason: 69 | type: string 70 | type: 71 | type: string 72 | required: 73 | - logicalID 74 | - physicalID 75 | - status 76 | - type 77 | type: object 78 | nullable: true 79 | type: array 80 | stackID: 81 | type: string 82 | stackStatus: 83 | type: string 84 | updatedTime: 85 | format: date-time 86 | nullable: true 87 | type: string 88 | required: 89 | - stackID 90 | type: object 91 | type: object 92 | served: true 93 | storage: true 94 | subresources: 95 | status: {} -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Thank you for installing the {{ .Chart.Name }} chart. 2 | 3 | For more information on configuring {{ .Chart.Name }}, refer to {{ .Chart.Home }} 4 | 5 | Your release is named {{ .Release.Name }}. 6 | 7 | To learn more about the release, try: 8 | 9 | $ helm status {{ .Release.Name }} 10 | $ helm get {{ .Release.Name }} -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "cloudformation-operator.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "cloudformation-operator.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "cloudformation-operator.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "cloudformation-operator.labels" -}} 37 | helm.sh/chart: {{ include "cloudformation-operator.chart" . }} 38 | {{ include "cloudformation-operator.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "cloudformation-operator.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "cloudformation-operator.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "cloudformation-operator.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "cloudformation-operator.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: cloudformation-operator-manager-role 6 | labels: 7 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 8 | rules: 9 | - apiGroups: 10 | - cloudformation.linki.space 11 | resources: 12 | - stacks 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - cloudformation.linki.space 23 | resources: 24 | - stacks/finalizers 25 | verbs: 26 | - update 27 | - apiGroups: 28 | - cloudformation.linki.space 29 | resources: 30 | - stacks/status 31 | verbs: 32 | - get 33 | - patch 34 | - update 35 | --- 36 | apiVersion: rbac.authorization.k8s.io/v1 37 | kind: ClusterRole 38 | metadata: 39 | name: cloudformation-operator-metrics-reader 40 | labels: 41 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 42 | rules: 43 | - nonResourceURLs: 44 | - /metrics 45 | verbs: 46 | - get 47 | --- 48 | apiVersion: rbac.authorization.k8s.io/v1 49 | kind: ClusterRole 50 | metadata: 51 | name: cloudformation-operator-proxy-role 52 | labels: 53 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 54 | rules: 55 | - apiGroups: 56 | - authentication.k8s.io 57 | resources: 58 | - tokenreviews 59 | verbs: 60 | - create 61 | - apiGroups: 62 | - authorization.k8s.io 63 | resources: 64 | - subjectaccessreviews 65 | verbs: 66 | - create 67 | {{- end -}} -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: cloudformation-operator-manager-rolebinding 6 | labels: 7 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: cloudformation-operator-manager-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: {{ include "cloudformation-operator.serviceAccountName" . }} 15 | namespace: {{ .Release.Namespace }} 16 | --- 17 | apiVersion: rbac.authorization.k8s.io/v1 18 | kind: ClusterRoleBinding 19 | metadata: 20 | name: cloudformation-operator-proxy-rolebinding 21 | labels: 22 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 23 | roleRef: 24 | apiGroup: rbac.authorization.k8s.io 25 | kind: ClusterRole 26 | name: cloudformation-operator-proxy-role 27 | subjects: 28 | - kind: ServiceAccount 29 | name: {{ include "cloudformation-operator.serviceAccountName" . }} 30 | namespace: {{ .Release.Namespace }} 31 | {{- end }} -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: cloudformation-operator-manager-config 5 | labels: 6 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 7 | data: 8 | controller_manager_config.yaml: | 9 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 10 | kind: ControllerManagerConfig 11 | health: 12 | healthProbeBindAddress: :8081 13 | metrics: 14 | bindAddress: 127.0.0.1:8080 15 | webhook: 16 | port: 9443 17 | leaderElection: 18 | leaderElect: true 19 | resourceName: 64032969.cloudformation.linki.space 20 | -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "cloudformation-operator.fullname" . }} 5 | labels: 6 | control-plane: controller-manager 7 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 8 | spec: 9 | replicas: {{ .Values.replicas }} 10 | selector: 11 | matchLabels: 12 | control-plane: controller-manager 13 | template: 14 | metadata: 15 | annotations: 16 | {{- if .Values.podAnnotations }} 17 | {{ toYaml .Values.podAnnotations | indent 8 }} 18 | {{- end }} 19 | labels: 20 | control-plane: controller-manager 21 | {{- include "cloudformation-operator.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | {{- if .Values.nodeSelector }} 28 | nodeSelector: 29 | {{ toYaml .Values.nodeSelector | indent 8 }} 30 | {{ end }} 31 | serviceAccountName: {{ include "cloudformation-operator.serviceAccountName" . }} 32 | securityContext: 33 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 34 | terminationGracePeriodSeconds: 10 35 | containers: 36 | - args: 37 | - --secure-listen-address=0.0.0.0:8443 38 | - --upstream=http://127.0.0.1:8080/ 39 | - --logtostderr=true 40 | - --v=10 41 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 42 | imagePullPolicy: {{ .Values.image.pullPolicy }} 43 | name: kube-rbac-proxy 44 | ports: 45 | - containerPort: 8443 46 | name: https 47 | - name: manager 48 | {{- with .Values.image }} 49 | image: "{{ .repository }}{{- if .sha }}@{{ .sha }}{{ else }}:{{ .tag | default "latest" }}{{ end }}" 50 | {{- end }} 51 | imagePullPolicy: {{ .Values.image.pullPolicy }} 52 | command: 53 | - /manager 54 | args: 55 | - --health-probe-bind-address=:8081 56 | - --metrics-bind-address=127.0.0.1:8080 57 | - --leader-elect 58 | {{- if .Values.tags }} 59 | {{- range $key, $value := .Values.tags }} 60 | - --tag={{ $key }}={{ $value }} 61 | {{- end }} 62 | {{- end }} 63 | {{- if .Values.capability.enabled }} 64 | - --capability=CAPABILITY_IAM 65 | {{- end }} 66 | env: 67 | - name: AWS_REGION 68 | {{- if .Values.operator.region }} 69 | value: {{ .Values.operator.region }} 70 | {{- else }} 71 | value: eu-central-1 72 | {{- end }} 73 | {{- if .Values.extraEnv}} 74 | {{- range $key, $value := .Values.extraEnv }} 75 | - name: {{ $key }} 76 | value: {{ $value }} 77 | {{- end }} 78 | {{- end }} 79 | livenessProbe: 80 | httpGet: 81 | path: /healthz 82 | port: 8081 83 | initialDelaySeconds: 15 84 | periodSeconds: 20 85 | readinessProbe: 86 | httpGet: 87 | path: /readyz 88 | port: 8081 89 | initialDelaySeconds: 5 90 | periodSeconds: 10 91 | securityContext: 92 | allowPrivilegeEscalation: false 93 | {{- if .Values.resources }} 94 | resources: 95 | {{ toYaml .Values.resources | indent 12 }} 96 | {{- end }} 97 | {{- if .Values.extraVolumeMounts }} 98 | volumeMounts: 99 | {{ toYaml .Values.extraVolumeMounts | indent 12 }} 100 | {{- end }} 101 | {{- if .Values.extraVolumes }} 102 | volumes: 103 | {{ toYaml .Values.extraVolumes | indent 8 }} 104 | {{- end }} 105 | {{- if .Values.affinity }} 106 | affinity: 107 | {{ toYaml .Values.affinity | indent 8 }} 108 | {{- end }} 109 | terminationGracePeriodSeconds: 10 110 | {{- if .Values.tolerations }} 111 | tolerations: 112 | {{ toYaml .Values.tolerations | indent 8 }} 113 | {{- end }} 114 | -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/role.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: cloudformation-operator-leader-election-role 6 | labels: 7 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 8 | rules: 9 | - apiGroups: 10 | - "" 11 | - coordination.k8s.io 12 | resources: 13 | - configmaps 14 | - leases 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | - create 20 | - update 21 | - patch 22 | - delete 23 | - apiGroups: 24 | - "" 25 | resources: 26 | - events 27 | verbs: 28 | - create 29 | - patch 30 | {{- end -}} -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | kind: RoleBinding 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: cloudformation-operator-leader-election-rolebinding 6 | labels: 7 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 8 | subjects: 9 | - kind: ServiceAccount 10 | name: {{ include "cloudformation-operator.serviceAccountName" . }} 11 | roleRef: 12 | kind: Role 13 | name: cloudformation-operator-leader-election-role 14 | apiGroup: rbac.authorization.k8s.io 15 | {{- end }} -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: cloudformation-operator-controller-manager-metrics-service 5 | labels: 6 | control-plane: controller-manager 7 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager -------------------------------------------------------------------------------- /helm/cloudformation-operator/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "cloudformation-operator.serviceAccountName" . }} 6 | labels: 7 | {{- include "cloudformation-operator.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} -------------------------------------------------------------------------------- /helm/cloudformation-operator/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for cloudformation-operator 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | image: 5 | repository: quay.io/linki/cloudformation-operator 6 | tag: "" 7 | sha: "" 8 | pullPolicy: IfNotPresent 9 | 10 | replicas: 1 11 | 12 | imagePullSecrets: [] 13 | nameOverride: "" 14 | fullnameOverride: "" 15 | 16 | serviceAccount: 17 | # Specifies whether a service account should be created 18 | create: true 19 | # Annotations to add to the service account 20 | annotations: {} 21 | # The name of the service account to use. 22 | # If not set and create is true, a name is generated using the fullname template 23 | name: "" 24 | 25 | ## Pod Annotations 26 | podAnnotations: 27 | # iam.amazonaws.com/role: "arn:aws:iam:::role/cloudformation-operator" 28 | 29 | ## App config 30 | operator: 31 | region: eu-central-1 32 | 33 | #You may want to assign tags to your CloudFormation stacks. 34 | #The tags added to a CloudFormation stack will be propagated to the managed resources. 35 | tags: 36 | # wambo: baz 37 | # foo: bar 38 | 39 | #Enable specified capabilities for all stacks managed by the operator instance. 40 | capability: 41 | enabled: false 42 | 43 | ## RBAC roles and bindings 44 | rbac: 45 | create: true 46 | serviceAccountName: cloudformation-operator 47 | 48 | ## Pod Resources 49 | resources: 50 | requests: 51 | cpu: 5m 52 | memory: 45Mi 53 | 54 | ## Pod Affinity 55 | affinity: {} 56 | 57 | ## A list of additional environment variables 58 | extraEnv: 59 | #- name: my_env 60 | # value: my_value 61 | 62 | ## Additional Volumes and mounts 63 | extraVolumes: 64 | #- hostPath: 65 | # path: /var/log 66 | # name: logs 67 | extraVolumeMounts: 68 | #- name: logs 69 | # mountPath: /host/var/log 70 | # readOnly: true 71 | 72 | ## Node Selector 73 | nodeSelector: 74 | #disktype: ssd 75 | 76 | ## Pod Tolerations 77 | tolerations: 78 | #- key: "node.kubernetes.io/not-ready" 79 | # operator: "Exists" 80 | # effect: "NoExecute" 81 | # tolerationSeconds: 6000 82 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Martin Linkhorst 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package main 26 | 27 | import ( 28 | "context" 29 | "flag" 30 | "github.com/aws/aws-sdk-go-v2/config" 31 | "github.com/aws/aws-sdk-go-v2/credentials/stscreds" 32 | "github.com/aws/aws-sdk-go-v2/service/cloudformation" 33 | cfTypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" 34 | "github.com/aws/aws-sdk-go-v2/service/sts" 35 | 36 | "github.com/spf13/pflag" 37 | "os" 38 | 39 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 40 | // to ensure that exec-entrypoint and run can make use of them. 41 | _ "k8s.io/client-go/plugin/pkg/client/auth" 42 | 43 | "k8s.io/apimachinery/pkg/runtime" 44 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 45 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 46 | ctrl "sigs.k8s.io/controller-runtime" 47 | "sigs.k8s.io/controller-runtime/pkg/healthz" 48 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 49 | 50 | cloudformationv1alpha1 "github.com/linki/cloudformation-operator/api/v1alpha1" 51 | "github.com/linki/cloudformation-operator/controllers" 52 | // +kubebuilder:scaffold:imports 53 | ) 54 | 55 | var ( 56 | scheme = runtime.NewScheme() 57 | setupLog = ctrl.Log.WithName("setup") 58 | StackFlagSet *pflag.FlagSet 59 | ) 60 | 61 | func init() { 62 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 63 | 64 | utilruntime.Must(cloudformationv1alpha1.AddToScheme(scheme)) 65 | // +kubebuilder:scaffold:scheme 66 | 67 | StackFlagSet = pflag.NewFlagSet("stack", pflag.ExitOnError) 68 | StackFlagSet.String("region", "", "The AWS region to use") 69 | StackFlagSet.String("assume-role", "", "Assume AWS role when defined. Useful for stacks in another AWS account. Specify the full ARN, e.g. `arn:aws:iam::123456789:role/cloudformation-operator`") 70 | StackFlagSet.StringToString("tag", map[string]string{}, "Tags to apply to all Stacks by default. Specify multiple times for multiple tags.") 71 | StackFlagSet.StringSlice("capability", []string{}, "The AWS CloudFormation capability to enable") 72 | StackFlagSet.Bool("dry-run", false, "If true, don't actually do anything.") 73 | } 74 | 75 | func main() { 76 | var namespace string 77 | var metricsAddr string 78 | var enableLeaderElection bool 79 | var probeAddr string 80 | 81 | flag.StringVar(&namespace, "namespace", "", "The Kubernetes namespace to watch") 82 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 83 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 84 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 85 | "Enable leader election for controller manager. "+ 86 | "Enabling this will ensure there is only one active controller manager.") 87 | opts := zap.Options{ 88 | Development: true, 89 | } 90 | opts.BindFlags(flag.CommandLine) 91 | 92 | pflag.CommandLine.AddGoFlagSet(flag.CommandLine) 93 | pflag.CommandLine.AddFlagSet(StackFlagSet) 94 | pflag.Parse() 95 | 96 | if namespace == "" { 97 | namespace = os.Getenv("WATCH_NAMESPACE") 98 | } 99 | 100 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 101 | 102 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 103 | Scheme: scheme, 104 | MetricsBindAddress: metricsAddr, 105 | Port: 9443, 106 | HealthProbeBindAddress: probeAddr, 107 | LeaderElection: enableLeaderElection, 108 | LeaderElectionID: "64032969.cloudformation.linki.space", 109 | Namespace: namespace, 110 | }) 111 | if err != nil { 112 | setupLog.Error(err, "unable to start manager") 113 | os.Exit(1) 114 | } 115 | 116 | assumeRole, err := StackFlagSet.GetString("assume-role") 117 | if err != nil { 118 | setupLog.Error(err, "error parsing flag") 119 | os.Exit(1) 120 | } 121 | region, err := StackFlagSet.GetString("region") 122 | if err != nil { 123 | setupLog.Error(err, "error parsing flag") 124 | os.Exit(1) 125 | } 126 | defaultTags, err := StackFlagSet.GetStringToString("tag") 127 | if err != nil { 128 | setupLog.Error(err, "error parsing flag") 129 | os.Exit(1) 130 | } 131 | 132 | paramStringSlice, err := StackFlagSet.GetStringSlice("capability") 133 | if err != nil { 134 | setupLog.Error(err, "error parsing flag") 135 | os.Exit(1) 136 | } 137 | defaultCapabilities := make([]cfTypes.Capability, len(paramStringSlice)) 138 | for i := range paramStringSlice { 139 | defaultCapabilities[i] = cfTypes.Capability(paramStringSlice[i]) 140 | } 141 | 142 | dryRun, err := StackFlagSet.GetBool("dry-run") 143 | if err != nil { 144 | setupLog.Error(err, "error parsing flag") 145 | os.Exit(1) 146 | } 147 | 148 | cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region)) 149 | if err != nil { 150 | setupLog.Error(err, "error getting AWS config") 151 | os.Exit(1) 152 | } 153 | creds := cfg.Credentials 154 | 155 | setupLog.Info(assumeRole) 156 | if assumeRole != "" { 157 | setupLog.Info("run assume") 158 | stsClient := sts.NewFromConfig(cfg) 159 | creds = stscreds.NewAssumeRoleProvider(stsClient, assumeRole) 160 | } 161 | 162 | client := cloudformation.NewFromConfig(cfg, func(o *cloudformation.Options) { 163 | o.Credentials = creds 164 | }) 165 | 166 | cfHelper := &controllers.CloudFormationHelper{ 167 | CloudFormation: client, 168 | } 169 | 170 | stackFollower := &controllers.StackFollower{ 171 | Client: mgr.GetClient(), 172 | Log: ctrl.Log.WithName("workers").WithName("Stack"), 173 | SubmissionChannel: make(chan *cloudformationv1alpha1.Stack), 174 | CloudFormationHelper: cfHelper, 175 | } 176 | go stackFollower.Receiver() 177 | go stackFollower.Worker() 178 | 179 | if err = (&controllers.StackReconciler{ 180 | Client: mgr.GetClient(), 181 | Log: ctrl.Log.WithName("controllers").WithName("Stack"), 182 | Scheme: mgr.GetScheme(), 183 | CloudFormation: client, 184 | StackFollower: stackFollower, 185 | CloudFormationHelper: cfHelper, 186 | DefaultTags: defaultTags, 187 | DefaultCapabilities: defaultCapabilities, 188 | DryRun: dryRun, 189 | }).SetupWithManager(mgr); err != nil { 190 | setupLog.Error(err, "unable to create controller", "controller", "Stack") 191 | os.Exit(1) 192 | } 193 | // +kubebuilder:scaffold:builder 194 | 195 | if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { 196 | setupLog.Error(err, "unable to set up health check") 197 | os.Exit(1) 198 | } 199 | if err := mgr.AddReadyzCheck("check", healthz.Ping); err != nil { 200 | setupLog.Error(err, "unable to set up ready check") 201 | os.Exit(1) 202 | } 203 | 204 | setupLog.Info("starting manager") 205 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 206 | setupLog.Error(err, "problem running manager") 207 | os.Exit(1) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "kubernetes": { 3 | "fileMatch": ["deploy/.+\\.yaml$"] 4 | } 5 | } 6 | --------------------------------------------------------------------------------