├── .gitignore ├── apis ├── operators │ ├── group.go │ └── v1alpha1 │ │ ├── doc.go │ │ ├── register.go │ │ ├── sandbox_types.go │ │ ├── zz_generated.deepcopy.go │ │ └── zz_generated.openapi.go ├── addtoscheme_operators_v1alpha1.go └── apis.go ├── img └── sandbox-operator.png ├── .gitattributes ├── .editorconfig ├── example ├── sandbox.yaml ├── kustomization.yaml └── sandbox-config.yaml ├── deploy ├── service-account.yaml ├── kustomization.yaml ├── cluster-role-binding.yaml ├── user-default-role.yaml ├── deployment.yaml ├── cluster-role.yaml └── sandbox-crd.yaml ├── scripts ├── user_setup └── entrypoint ├── Dockerfile ├── .github └── workflows │ └── ci.yml ├── Makefile ├── LICENSE ├── go.mod ├── controller ├── azure.go ├── sandbox_integration_test.go ├── sandbox_test.go └── sandbox.go ├── main.go ├── bundle.yaml ├── README.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | sandbox-operator 2 | *.exe -------------------------------------------------------------------------------- /apis/operators/group.go: -------------------------------------------------------------------------------- 1 | // Package operators contains operators.plex.dev API versions 2 | package operators 3 | -------------------------------------------------------------------------------- /img/sandbox-operator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plexsystems/sandbox-operator/HEAD/img/sandbox-operator.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | go.mod text eol=lf 3 | 4 | /scripts/entrypoint text eol=lf 5 | /scripts/user_setup text eol=lf 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{yml,yaml}] 4 | indent_style = space 5 | indent_size = 2 6 | trim_trailing_whitespace = true 7 | -------------------------------------------------------------------------------- /example/sandbox.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.plex.dev/v1alpha1 2 | kind: Sandbox 3 | metadata: 4 | name: test 5 | spec: 6 | size: small 7 | owners: 8 | - foo@bar.com 9 | -------------------------------------------------------------------------------- /apis/operators/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // Package v1alpha1 contains API Schema definitions for the operators.plex.dev API group 2 | // +k8s:deepcopy-gen=package,register 3 | // +groupName=operators.plex.dev 4 | package v1alpha1 5 | -------------------------------------------------------------------------------- /deploy/service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: sandbox-operator-sa 5 | labels: 6 | app.kubernetes.io/name: sandbox-operator 7 | app.kubernetes.io/part-of: sandbox-operator 8 | -------------------------------------------------------------------------------- /deploy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - deployment.yaml 6 | - cluster-role-binding.yaml 7 | - cluster-role.yaml 8 | - sandbox-crd.yaml 9 | - service-account.yaml 10 | - user-default-role.yaml 11 | -------------------------------------------------------------------------------- /apis/addtoscheme_operators_v1alpha1.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "github.com/plexsystems/sandbox-operator/apis/operators/v1alpha1" 5 | ) 6 | 7 | func init() { 8 | // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back 9 | AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme) 10 | } 11 | -------------------------------------------------------------------------------- /apis/apis.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/runtime" 5 | ) 6 | 7 | // AddToSchemes may be used to add all resources defined in the project to a Scheme 8 | var AddToSchemes runtime.SchemeBuilder 9 | 10 | // AddToScheme adds all Resources to the Scheme 11 | func AddToScheme(s *runtime.Scheme) error { 12 | return AddToSchemes.AddToScheme(s) 13 | } 14 | -------------------------------------------------------------------------------- /scripts/user_setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | 4 | # ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be) 5 | mkdir -p ${HOME} 6 | chown ${USER_UID}:0 ${HOME} 7 | chmod ug+rwx ${HOME} 8 | 9 | # runtime user will need to be able to self-insert in /etc/passwd 10 | chmod g+rw /etc/passwd 11 | 12 | # no need for this script to remain in the image after running 13 | rm $0 14 | -------------------------------------------------------------------------------- /example/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: default 4 | 5 | resources: 6 | - ../deploy 7 | 8 | patchesStrategicMerge: 9 | - sandbox-config.yaml 10 | 11 | # NOTE: This is used for testing only 12 | # It is recommended to use the latest version of the operator 13 | images: 14 | - name: plexsystems/sandbox-operator 15 | newName: sandbox-operator 16 | newTag: dev 17 | -------------------------------------------------------------------------------- /scripts/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # This is documented here: 4 | # https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines 5 | 6 | if ! whoami &>/dev/null; then 7 | if [ -w /etc/passwd ]; then 8 | echo "${USER_NAME:-sandbox-operator}:x:$(id -u):$(id -g):${USER_NAME:-sandbox-operator} user:${HOME}:/sbin/nologin" >> /etc/passwd 9 | fi 10 | fi 11 | 12 | exec ${OPERATOR} $@ 13 | -------------------------------------------------------------------------------- /deploy/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | kind: ClusterRoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: sandbox-operator 5 | labels: 6 | app.kubernetes.io/name: sandbox-operator 7 | app.kubernetes.io/part-of: sandbox-operator 8 | subjects: 9 | - kind: ServiceAccount 10 | name: sandbox-operator-sa 11 | namespace: default 12 | roleRef: 13 | kind: ClusterRole 14 | name: sandbox-operator 15 | apiGroup: rbac.authorization.k8s.io 16 | -------------------------------------------------------------------------------- /example/sandbox-config.yaml: -------------------------------------------------------------------------------- 1 | # Example configuration providing Azure credentials to the operator 2 | 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: sandbox-operator 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: sandbox-operator 12 | env: 13 | - name: AZURE_CLIENT_ID 14 | value: "" 15 | - name: AZURE_TENANT_ID 16 | value: "" 17 | - name: AZURE_CLIENT_SECRET 18 | value: "" 19 | - name: AZURE_AD_RESOURCE 20 | value: "https://graph.windows.net" 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.13-alpine AS builder 2 | WORKDIR /operator 3 | 4 | COPY go.mod . 5 | COPY go.sum . 6 | RUN go mod download 7 | 8 | COPY . . 9 | 10 | RUN GOOS=linux GOARCH=amd64 go build -o sandbox-operator main.go 11 | 12 | FROM alpine:3.11.6 13 | ENV OPERATOR=/usr/local/bin/sandbox-operator \ 14 | USER_UID=1001 \ 15 | USER_NAME=sandbox-operator 16 | 17 | COPY --from=builder /operator/sandbox-operator ${OPERATOR} 18 | COPY scripts/ /usr/local/bin 19 | 20 | RUN chmod +x /usr/local/bin/user_setup 21 | RUN chmod +x /usr/local/bin/entrypoint 22 | RUN chmod +x /usr/local/bin/sandbox-operator 23 | 24 | RUN /usr/local/bin/user_setup 25 | 26 | ENTRYPOINT ["/usr/local/bin/entrypoint"] 27 | 28 | USER ${USER_UID} 29 | -------------------------------------------------------------------------------- /apis/operators/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | // NOTE: Boilerplate only. Ignore this file. 2 | 3 | // Package v1alpha1 contains API Schema definitions for the operators.plex.dev API group 4 | // +k8s:deepcopy-gen=package,register 5 | // +groupName=operators.plex.dev 6 | package v1alpha1 7 | 8 | import ( 9 | "k8s.io/apimachinery/pkg/runtime/schema" 10 | "sigs.k8s.io/controller-runtime/pkg/runtime/scheme" 11 | ) 12 | 13 | var ( 14 | // SchemeGroupVersion is group version used to register these objects 15 | SchemeGroupVersion = schema.GroupVersion{Group: "operators.plex.dev", Version: "v1alpha1"} 16 | 17 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 18 | SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} 19 | ) 20 | -------------------------------------------------------------------------------- /deploy/user-default-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: sandbox-user 5 | labels: 6 | app.kubernetes.io/name: sandbox-operator 7 | app.kubernetes.io/part-of: sandbox-operator 8 | rules: 9 | - apiGroups: 10 | - "operators.plex.dev" 11 | resources: 12 | - sandboxes 13 | verbs: 14 | - create 15 | - list 16 | - get 17 | --- 18 | apiVersion: rbac.authorization.k8s.io/v1 19 | kind: ClusterRoleBinding 20 | metadata: 21 | name: sandbox-users 22 | labels: 23 | app.kubernetes.io/name: sandbox-operator 24 | app.kubernetes.io/part-of: sandbox-operator 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: sandbox-user 29 | subjects: 30 | - kind: Group 31 | name: system:authenticated 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: operator 2 | on: pull_request 3 | 4 | jobs: 5 | pr-validation: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: checkout source 9 | uses: actions/checkout@v1 10 | 11 | - name: setup go 12 | uses: actions/setup-go@v1 13 | with: 14 | go-version: '1.13.5' 15 | 16 | - name: golangci-lint 17 | uses: reviewdog/action-golangci-lint@v1 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | 21 | - name: unit tests 22 | run: make test-unit 23 | 24 | - name: setup kind 25 | uses: engineerd/setup-kind@v0.2.0 26 | with: 27 | version: "v0.6.1" 28 | 29 | - name: setup kustomize 30 | uses: imranismail/setup-kustomize@master 31 | with: 32 | kustomize-version: "3.4.0" 33 | 34 | - name: integration tests 35 | run: make test-integration 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | KUBERNETES_VERSION=v1.14.10 2 | CLUSTER_NAME=operator-testing-$(KUBERNETES_VERSION) 3 | OPERATOR_IMAGE=sandbox-operator:dev 4 | 5 | .PHONY: image 6 | image: 7 | docker build . -t $(OPERATOR_IMAGE) 8 | 9 | .PHONY: cluster 10 | cluster: 11 | kind create cluster --name $(CLUSTER_NAME) --image kindest/node:$(KUBERNETES_VERSION) 12 | kubectl wait --for=condition=Ready --timeout=60s node --all 13 | 14 | .PHONY: deploy 15 | deploy: image 16 | kind load docker-image $(OPERATOR_IMAGE) --name $(CLUSTER_NAME) 17 | kubectl delete pod --all 18 | kustomize build example | kubectl apply -f - 19 | kubectl wait --for=condition=Ready --timeout=60s pods --all 20 | 21 | .PHONY: test-unit 22 | test-unit: 23 | go test ./controller -v -count=1 24 | 25 | .PHONY: test-integration 26 | test-integration: cluster deploy 27 | go test ./controller -v --tags=integration -count=1 28 | 29 | .PHONY: destroy 30 | destroy: 31 | kind delete cluster --name $(CLUSTER_NAME) 32 | -------------------------------------------------------------------------------- /deploy/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sandbox-operator 5 | labels: 6 | app.kubernetes.io/name: sandbox-operator 7 | app.kubernetes.io/version: v0.10.1 8 | spec: 9 | selector: 10 | matchLabels: 11 | name: sandbox-operator 12 | template: 13 | metadata: 14 | labels: 15 | name: sandbox-operator 16 | spec: 17 | serviceAccountName: sandbox-operator-sa 18 | containers: 19 | - name: sandbox-operator 20 | image: plexsystems/sandbox-operator:v0.10.1 21 | command: 22 | - sandbox-operator 23 | imagePullPolicy: IfNotPresent 24 | env: 25 | - name: OPERATOR_NAME 26 | value: "sandbox-operator" 27 | - name: WATCH_NAMESPACE 28 | valueFrom: 29 | fieldRef: 30 | fieldPath: metadata.namespace 31 | - name: POD_NAME 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.name 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Plex Systems 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 | -------------------------------------------------------------------------------- /apis/operators/v1alpha1/sandbox_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // SandboxSpec defines the desired state of Sandbox 8 | // +k8s:openapi-gen=true 9 | type SandboxSpec struct { 10 | Owners []string `json:"owners"` 11 | Size string `json:"size"` 12 | } 13 | 14 | // SandboxStatus defines the observed state of Sandbox 15 | // +k8s:openapi-gen=true 16 | type SandboxStatus struct{} 17 | 18 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 19 | 20 | // Sandbox is the Schema for the sandboxes API 21 | // +k8s:openapi-gen=true 22 | // +kubebuilder:subresource:status 23 | type Sandbox struct { 24 | metav1.TypeMeta `json:",inline"` 25 | metav1.ObjectMeta `json:"metadata,omitempty"` 26 | 27 | Spec SandboxSpec `json:"spec,omitempty"` 28 | Status SandboxStatus `json:"status,omitempty"` 29 | } 30 | 31 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 32 | 33 | // SandboxList contains a list of Sandbox 34 | type SandboxList struct { 35 | metav1.TypeMeta `json:",inline"` 36 | metav1.ListMeta `json:"metadata,omitempty"` 37 | Items []Sandbox `json:"items"` 38 | } 39 | 40 | func init() { 41 | SchemeBuilder.Register(&Sandbox{}, &SandboxList{}) 42 | } 43 | -------------------------------------------------------------------------------- /deploy/cluster-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: sandbox-operator 5 | labels: 6 | app.kubernetes.io/name: sandbox-operator 7 | app.kubernetes.io/part-of: sandbox-operator 8 | rules: 9 | - apiGroups: 10 | - "" 11 | resources: 12 | - pods 13 | - services 14 | - services/finalizers 15 | - endpoints 16 | - persistentvolumeclaims 17 | - events 18 | - configmaps 19 | - secrets 20 | - namespaces 21 | - resourcequotas 22 | - serviceaccounts 23 | verbs: 24 | - '*' 25 | - apiGroups: 26 | - apps 27 | resources: 28 | - deployments 29 | - daemonsets 30 | - replicasets 31 | - statefulsets 32 | verbs: 33 | - '*' 34 | - apiGroups: 35 | - monitoring.coreos.com 36 | resources: 37 | - servicemonitors 38 | verbs: 39 | - get 40 | - create 41 | - apiGroups: 42 | - apps 43 | resourceNames: 44 | - sanbox-operator 45 | resources: 46 | - deployments/finalizers 47 | verbs: 48 | - update 49 | - apiGroups: 50 | - "" 51 | resources: 52 | - pods 53 | verbs: 54 | - get 55 | - apiGroups: 56 | - apps 57 | resources: 58 | - replicasets 59 | verbs: 60 | - get 61 | - apiGroups: 62 | - operators.plex.dev 63 | resources: 64 | - '*' 65 | verbs: 66 | - '*' 67 | - apiGroups: 68 | - rbac.authorization.k8s.io 69 | resources: 70 | - '*' 71 | verbs: 72 | - '*' 73 | -------------------------------------------------------------------------------- /deploy/sandbox-crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: sandboxes.operators.plex.dev 5 | spec: 6 | group: operators.plex.dev 7 | names: 8 | kind: Sandbox 9 | listKind: SandboxList 10 | plural: sandboxes 11 | singular: sandbox 12 | scope: Cluster 13 | subresources: 14 | status: {} 15 | validation: 16 | openAPIV3Schema: 17 | properties: 18 | apiVersion: 19 | description: 'APIVersion defines the versioned schema of this representation 20 | of an object. Servers should convert recognized schemas to the latest 21 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' 22 | type: string 23 | kind: 24 | description: 'Kind is a string value representing the REST resource this 25 | object represents. Servers may infer this from the endpoint the client 26 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' 27 | type: string 28 | metadata: 29 | type: object 30 | spec: 31 | type: object 32 | status: 33 | type: object 34 | version: v1alpha1 35 | versions: 36 | - name: v1alpha1 37 | served: true 38 | storage: true 39 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/plexsystems/sandbox-operator 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Azure/azure-sdk-for-go v32.5.0+incompatible 7 | github.com/Azure/go-autorest v13.3.3+incompatible // indirect 8 | github.com/Azure/go-autorest/autorest v0.9.5 // indirect 9 | github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 10 | github.com/Azure/go-autorest/autorest/to v0.3.0 // indirect 11 | github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect 12 | github.com/go-openapi/spec v0.19.2 13 | github.com/operator-framework/operator-sdk v0.11.0 14 | k8s.io/api v0.15.7 15 | k8s.io/apimachinery v0.15.7 16 | k8s.io/client-go v12.0.0+incompatible 17 | k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d 18 | sigs.k8s.io/controller-runtime v0.3.0 19 | ) 20 | 21 | replace ( 22 | k8s.io/api => k8s.io/api v0.15.7 23 | k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.15.7 24 | k8s.io/apimachinery => k8s.io/apimachinery v0.15.7 25 | k8s.io/apiserver => k8s.io/apiserver v0.15.7 26 | k8s.io/cli-runtime => k8s.io/cli-runtime v0.15.7 27 | k8s.io/client-go => k8s.io/client-go v0.15.7 28 | k8s.io/cloud-provider => k8s.io/cloud-provider v0.15.7 29 | k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.15.7 30 | k8s.io/code-generator => k8s.io/code-generator v0.15.7 31 | k8s.io/component-base => k8s.io/component-base v0.15.7 32 | k8s.io/cri-api => k8s.io/cri-api v0.15.7 33 | k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.15.7 34 | k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.15.7 35 | k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.15.7 36 | k8s.io/kube-proxy => k8s.io/kube-proxy v0.15.7 37 | k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.15.7 38 | k8s.io/kubectl => k8s.io/kubectl v0.15.7 39 | k8s.io/kubelet => k8s.io/kubelet v0.15.7 40 | k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.15.7 41 | k8s.io/metrics => k8s.io/metrics v0.15.7 42 | k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.15.7 43 | ) 44 | -------------------------------------------------------------------------------- /controller/azure.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" 11 | "github.com/Azure/go-autorest/autorest/azure/auth" 12 | rbacv1 "k8s.io/api/rbac/v1" 13 | ) 14 | 15 | // AzureSubjects is a client that connects to Azure to get a users ObjectID 16 | type AzureSubjects struct { 17 | client graphrbac.UsersClient 18 | } 19 | 20 | // NewAzureSubjectsClient creates a new client to get azure users 21 | func NewAzureSubjectsClient() (*AzureSubjects, error) { 22 | const authorizeTimeout = 5 * time.Second 23 | 24 | ctx, cancel := context.WithTimeout(context.TODO(), authorizeTimeout) 25 | defer cancel() 26 | 27 | authorizer, err := auth.NewAuthorizerFromEnvironment() 28 | if err != nil { 29 | return nil, fmt.Errorf("new authorizer: %w", err) 30 | } 31 | 32 | graphClient := graphrbac.NewUsersClient(os.Getenv("AZURE_TENANT_ID")) 33 | graphClient.Authorizer = authorizer 34 | 35 | if _, err := graphClient.List(ctx, ""); err != nil { 36 | return nil, fmt.Errorf("list users: %w", err) 37 | } 38 | 39 | azureSubjects := AzureSubjects{ 40 | client: graphClient, 41 | } 42 | 43 | return &azureSubjects, nil 44 | } 45 | 46 | // Subjects gets the ObjectIDs from a list of given emails or user principal names 47 | func (a *AzureSubjects) Subjects(ctx context.Context, users []string) ([]rbacv1.Subject, error) { 48 | var objectIDs []string 49 | 50 | for _, user := range users { 51 | filterString := "mail eq '" + user + "' or userPrincipalName eq '" + user + "'" 52 | userListResultPage, err := a.client.List(ctx, filterString) 53 | if err != nil { 54 | return nil, fmt.Errorf("list users: %w", err) 55 | } 56 | 57 | userListResultPageValues := userListResultPage.Values() 58 | if userListResultPageValues != nil { 59 | objectIDs = append(objectIDs, (*userListResultPageValues[0].ObjectID)) 60 | } else { 61 | log.Printf("%s could not be found\n", user) 62 | } 63 | } 64 | 65 | subjects := getSubjects(objectIDs) 66 | 67 | return subjects, nil 68 | } 69 | -------------------------------------------------------------------------------- /apis/operators/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | // Code generated by operator-sdk. DO NOT EDIT. 4 | 5 | package v1alpha1 6 | 7 | import ( 8 | runtime "k8s.io/apimachinery/pkg/runtime" 9 | ) 10 | 11 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 12 | func (in *Sandbox) DeepCopyInto(out *Sandbox) { 13 | *out = *in 14 | out.TypeMeta = in.TypeMeta 15 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 16 | in.Spec.DeepCopyInto(&out.Spec) 17 | out.Status = in.Status 18 | return 19 | } 20 | 21 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sandbox. 22 | func (in *Sandbox) DeepCopy() *Sandbox { 23 | if in == nil { 24 | return nil 25 | } 26 | out := new(Sandbox) 27 | in.DeepCopyInto(out) 28 | return out 29 | } 30 | 31 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 32 | func (in *Sandbox) DeepCopyObject() runtime.Object { 33 | if c := in.DeepCopy(); c != nil { 34 | return c 35 | } 36 | return nil 37 | } 38 | 39 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 40 | func (in *SandboxList) DeepCopyInto(out *SandboxList) { 41 | *out = *in 42 | out.TypeMeta = in.TypeMeta 43 | out.ListMeta = in.ListMeta 44 | if in.Items != nil { 45 | in, out := &in.Items, &out.Items 46 | *out = make([]Sandbox, len(*in)) 47 | for i := range *in { 48 | (*in)[i].DeepCopyInto(&(*out)[i]) 49 | } 50 | } 51 | return 52 | } 53 | 54 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SandboxList. 55 | func (in *SandboxList) DeepCopy() *SandboxList { 56 | if in == nil { 57 | return nil 58 | } 59 | out := new(SandboxList) 60 | in.DeepCopyInto(out) 61 | return out 62 | } 63 | 64 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 65 | func (in *SandboxList) DeepCopyObject() runtime.Object { 66 | if c := in.DeepCopy(); c != nil { 67 | return c 68 | } 69 | return nil 70 | } 71 | 72 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 73 | func (in *SandboxSpec) DeepCopyInto(out *SandboxSpec) { 74 | *out = *in 75 | if in.Owners != nil { 76 | in, out := &in.Owners, &out.Owners 77 | *out = make([]string, len(*in)) 78 | copy(*out, *in) 79 | } 80 | return 81 | } 82 | 83 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SandboxSpec. 84 | func (in *SandboxSpec) DeepCopy() *SandboxSpec { 85 | if in == nil { 86 | return nil 87 | } 88 | out := new(SandboxSpec) 89 | in.DeepCopyInto(out) 90 | return out 91 | } 92 | 93 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 94 | func (in *SandboxStatus) DeepCopyInto(out *SandboxStatus) { 95 | *out = *in 96 | return 97 | } 98 | 99 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SandboxStatus. 100 | func (in *SandboxStatus) DeepCopy() *SandboxStatus { 101 | if in == nil { 102 | return nil 103 | } 104 | out := new(SandboxStatus) 105 | in.DeepCopyInto(out) 106 | return out 107 | } 108 | -------------------------------------------------------------------------------- /apis/operators/v1alpha1/zz_generated.openapi.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | // This file was autogenerated by openapi-gen. Do not edit it manually! 4 | 5 | package v1alpha1 6 | 7 | import ( 8 | spec "github.com/go-openapi/spec" 9 | common "k8s.io/kube-openapi/pkg/common" 10 | ) 11 | 12 | func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { 13 | return map[string]common.OpenAPIDefinition{ 14 | "./pkg/apis/operators/v1alpha1.Sandbox": schema_pkg_apis_operators_v1alpha1_Sandbox(ref), 15 | "./pkg/apis/operators/v1alpha1.SandboxSpec": schema_pkg_apis_operators_v1alpha1_SandboxSpec(ref), 16 | "./pkg/apis/operators/v1alpha1.SandboxStatus": schema_pkg_apis_operators_v1alpha1_SandboxStatus(ref), 17 | } 18 | } 19 | 20 | func schema_pkg_apis_operators_v1alpha1_Sandbox(ref common.ReferenceCallback) common.OpenAPIDefinition { 21 | return common.OpenAPIDefinition{ 22 | Schema: spec.Schema{ 23 | SchemaProps: spec.SchemaProps{ 24 | Description: "Sandbox is the Schema for the sandboxes API", 25 | Properties: map[string]spec.Schema{ 26 | "kind": { 27 | SchemaProps: spec.SchemaProps{ 28 | 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/api-conventions.md#types-kinds", 29 | Type: []string{"string"}, 30 | Format: "", 31 | }, 32 | }, 33 | "apiVersion": { 34 | SchemaProps: spec.SchemaProps{ 35 | 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/api-conventions.md#resources", 36 | Type: []string{"string"}, 37 | Format: "", 38 | }, 39 | }, 40 | "metadata": { 41 | SchemaProps: spec.SchemaProps{ 42 | Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), 43 | }, 44 | }, 45 | "spec": { 46 | SchemaProps: spec.SchemaProps{ 47 | Ref: ref("./pkg/apis/operators/v1alpha1.SandboxSpec"), 48 | }, 49 | }, 50 | "status": { 51 | SchemaProps: spec.SchemaProps{ 52 | Ref: ref("./pkg/apis/operators/v1alpha1.SandboxStatus"), 53 | }, 54 | }, 55 | }, 56 | }, 57 | }, 58 | Dependencies: []string{ 59 | "./pkg/apis/operators/v1alpha1.SandboxSpec", "./pkg/apis/operators/v1alpha1.SandboxStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, 60 | } 61 | } 62 | 63 | func schema_pkg_apis_operators_v1alpha1_SandboxSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 64 | return common.OpenAPIDefinition{ 65 | Schema: spec.Schema{ 66 | SchemaProps: spec.SchemaProps{ 67 | Description: "SandboxSpec defines the desired state of Sandbox", 68 | Properties: map[string]spec.Schema{}, 69 | }, 70 | }, 71 | Dependencies: []string{}, 72 | } 73 | } 74 | 75 | func schema_pkg_apis_operators_v1alpha1_SandboxStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { 76 | return common.OpenAPIDefinition{ 77 | Schema: spec.Schema{ 78 | SchemaProps: spec.SchemaProps{ 79 | Description: "SandboxStatus defines the observed state of Sandbox", 80 | Properties: map[string]spec.Schema{}, 81 | }, 82 | }, 83 | Dependencies: []string{}, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /controller/sandbox_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package controller 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "testing" 9 | "time" 10 | 11 | "github.com/plexsystems/sandbox-operator/apis" 12 | operatorsv1alpha1 "github.com/plexsystems/sandbox-operator/apis/operators/v1alpha1" 13 | 14 | corev1 "k8s.io/api/core/v1" 15 | rbacv1 "k8s.io/api/rbac/v1" 16 | "k8s.io/apimachinery/pkg/api/errors" 17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 | "k8s.io/apimachinery/pkg/types" 19 | "k8s.io/apimachinery/pkg/util/wait" 20 | "k8s.io/client-go/kubernetes/scheme" 21 | ) 22 | 23 | func TestSandboxControllerIntegration(t *testing.T) { 24 | ctx := context.TODO() 25 | 26 | s := scheme.Scheme 27 | s.AddKnownTypes(operatorsv1alpha1.SchemeGroupVersion, &operatorsv1alpha1.Sandbox{}) 28 | apis.AddToScheme(s) 29 | 30 | client, err := NewClient(s) 31 | if err != nil { 32 | t.Fatalf("new client: %v", err) 33 | } 34 | 35 | sandbox := operatorsv1alpha1.Sandbox{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Name: "test", 38 | }, 39 | Spec: operatorsv1alpha1.SandboxSpec{ 40 | Size: "small", 41 | Owners: []string{"foo@bar.com"}, 42 | }, 43 | } 44 | 45 | if err := client.Create(ctx, &sandbox); err != nil { 46 | t.Fatalf("create sandbox: %v", err) 47 | } 48 | 49 | const intervalTime = 5 * time.Second 50 | const waitTime = 30 * time.Second 51 | 52 | namespace := getNamespace(sandbox) 53 | err = wait.PollImmediate(intervalTime, waitTime, func() (bool, error) { 54 | geterr := client.Get(ctx, types.NamespacedName{Name: namespace.Name}, &corev1.Namespace{}) 55 | if geterr == nil { 56 | return true, nil 57 | } else if errors.IsNotFound(geterr) { 58 | return false, nil 59 | } else { 60 | return false, fmt.Errorf("get namespace: %w", geterr) 61 | } 62 | }) 63 | if err != nil { 64 | t.Errorf("namespace not found: %v", err) 65 | } 66 | 67 | role := getRole(sandbox) 68 | err = wait.PollImmediate(intervalTime, waitTime, func() (bool, error) { 69 | geterr := client.Get(ctx, types.NamespacedName{Namespace: role.Namespace, Name: role.Name}, &rbacv1.Role{}) 70 | if geterr == nil { 71 | return true, nil 72 | } else if errors.IsNotFound(geterr) { 73 | return false, nil 74 | } else { 75 | return false, fmt.Errorf("get role: %w", geterr) 76 | } 77 | }) 78 | if err != nil { 79 | t.Errorf("role not found: %v", err) 80 | } 81 | 82 | resourceQuota := getResourceQuota(sandbox) 83 | err = wait.PollImmediate(intervalTime, waitTime, func() (bool, error) { 84 | geterr := client.Get(ctx, types.NamespacedName{Namespace: resourceQuota.Namespace, Name: resourceQuota.Name}, &corev1.ResourceQuota{}) 85 | if geterr == nil { 86 | return true, nil 87 | } else if errors.IsNotFound(geterr) { 88 | return false, nil 89 | } else { 90 | return false, fmt.Errorf("get resourcequota: %w", geterr) 91 | } 92 | }) 93 | if err != nil { 94 | t.Errorf("resourcequota not found: %v", err) 95 | } 96 | 97 | if err := client.Delete(ctx, &sandbox); err != nil { 98 | t.Fatalf("delete sandbox: %v", err) 99 | } 100 | 101 | err = wait.PollImmediate(intervalTime, waitTime, func() (bool, error) { 102 | geterr := client.Get(ctx, types.NamespacedName{Name: namespace.Name}, &corev1.Namespace{}) 103 | if errors.IsNotFound(geterr) { 104 | return true, nil 105 | } else if err == nil { 106 | return false, nil 107 | } else { 108 | return false, fmt.Errorf("get namespace: %v", err) 109 | } 110 | }) 111 | if err != nil { 112 | t.Errorf("namespace not deleted: %v", err) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "runtime" 8 | 9 | "github.com/plexsystems/sandbox-operator/apis" 10 | "github.com/plexsystems/sandbox-operator/controller" 11 | 12 | "github.com/operator-framework/operator-sdk/pkg/k8sutil" 13 | kubemetrics "github.com/operator-framework/operator-sdk/pkg/kube-metrics" 14 | "github.com/operator-framework/operator-sdk/pkg/leader" 15 | "github.com/operator-framework/operator-sdk/pkg/metrics" 16 | "github.com/operator-framework/operator-sdk/pkg/restmapper" 17 | v1 "k8s.io/api/core/v1" 18 | "k8s.io/apimachinery/pkg/util/intstr" 19 | "k8s.io/client-go/rest" 20 | "sigs.k8s.io/controller-runtime/pkg/client/config" 21 | "sigs.k8s.io/controller-runtime/pkg/manager" 22 | "sigs.k8s.io/controller-runtime/pkg/runtime/signals" 23 | ) 24 | 25 | var ( 26 | metricsHost = "0.0.0.0" 27 | metricsPort int32 = 8383 28 | operatorMetricsPort int32 = 8686 29 | version = "v0.10.1" 30 | ) 31 | 32 | func main() { 33 | namespace, err := k8sutil.GetWatchNamespace() 34 | if err != nil { 35 | log.Fatalf("watch namespace: %v", err) 36 | } 37 | 38 | cfg, err := config.GetConfig() 39 | if err != nil { 40 | log.Fatalf("get config: %v", err) 41 | } 42 | 43 | err = leader.Become(context.TODO(), "sandbox-operator-lock") 44 | if err != nil { 45 | log.Fatalf("leader promotion: %v", err) 46 | } 47 | 48 | mgr, err := manager.New(cfg, manager.Options{ 49 | Namespace: namespace, 50 | MapperProvider: restmapper.NewDynamicRESTMapper, 51 | MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort), 52 | }) 53 | if err != nil { 54 | log.Fatalf("new manager: %v", err) 55 | } 56 | 57 | if err := apis.AddToScheme(mgr.GetScheme()); err != nil { 58 | log.Fatalf("add crd scheme: %v", err) 59 | } 60 | 61 | if err := controller.Add(mgr); err != nil { 62 | log.Fatalf("add sandbox controller: %v", err) 63 | } 64 | 65 | service, err := serveMetrics(cfg) 66 | if err != nil { 67 | log.Fatalf("serve metrics: %v", err) 68 | } 69 | 70 | services := []*v1.Service{service} 71 | _, err = metrics.CreateServiceMonitors(cfg, namespace, services) 72 | if err != nil { 73 | if err == metrics.ErrServiceMonitorNotPresent { 74 | log.Println("prometheus-operator not found. skipping service monitor creation") 75 | } else { 76 | log.Printf("create service monitors: %v", err) 77 | } 78 | } 79 | 80 | log.Println("Starting operator...") 81 | log.Println(fmt.Sprintf("Version: %s", version)) 82 | log.Println(fmt.Sprintf("Go Version: %s", runtime.Version())) 83 | log.Println(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) 84 | 85 | if err := mgr.Start(signals.SetupSignalHandler()); err != nil { 86 | log.Fatalf("starting operator: %s", err) 87 | } 88 | } 89 | 90 | func serveMetrics(cfg *rest.Config) (*v1.Service, error) { 91 | customResourceKinds, err := k8sutil.GetGVKsFromAddToScheme(apis.AddToScheme) 92 | if err != nil { 93 | return nil, fmt.Errorf("get schemes: %w", err) 94 | } 95 | 96 | err = kubemetrics.GenerateAndServeCRMetrics(cfg, []string{""}, customResourceKinds, metricsHost, operatorMetricsPort) 97 | if err != nil { 98 | return nil, fmt.Errorf("serve metrics: %w", err) 99 | } 100 | 101 | servicePorts := []v1.ServicePort{ 102 | {Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}}, 103 | {Port: operatorMetricsPort, Name: metrics.CRPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: operatorMetricsPort}}, 104 | } 105 | 106 | service, err := metrics.CreateMetricsService(context.TODO(), cfg, servicePorts) 107 | if err != nil { 108 | return nil, fmt.Errorf("create metrics service: %w", err) 109 | } 110 | 111 | return service, nil 112 | } 113 | -------------------------------------------------------------------------------- /bundle.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: sandboxes.operators.plex.dev 5 | spec: 6 | group: operators.plex.dev 7 | names: 8 | kind: Sandbox 9 | listKind: SandboxList 10 | plural: sandboxes 11 | singular: sandbox 12 | scope: Cluster 13 | subresources: 14 | status: {} 15 | validation: 16 | openAPIV3Schema: 17 | properties: 18 | apiVersion: 19 | description: 'APIVersion defines the versioned schema of this representation 20 | of an object. Servers should convert recognized schemas to the latest 21 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' 22 | type: string 23 | kind: 24 | description: 'Kind is a string value representing the REST resource this 25 | object represents. Servers may infer this from the endpoint the client 26 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' 27 | type: string 28 | metadata: 29 | type: object 30 | spec: 31 | type: object 32 | status: 33 | type: object 34 | version: v1alpha1 35 | versions: 36 | - name: v1alpha1 37 | served: true 38 | storage: true 39 | --- 40 | apiVersion: v1 41 | kind: ServiceAccount 42 | metadata: 43 | labels: 44 | app.kubernetes.io/name: sandbox-operator 45 | app.kubernetes.io/part-of: sandbox-operator 46 | name: sandbox-operator-sa 47 | --- 48 | apiVersion: rbac.authorization.k8s.io/v1 49 | kind: ClusterRole 50 | metadata: 51 | labels: 52 | app.kubernetes.io/name: sandbox-operator 53 | app.kubernetes.io/part-of: sandbox-operator 54 | name: sandbox-operator 55 | rules: 56 | - apiGroups: 57 | - "" 58 | resources: 59 | - pods 60 | - services 61 | - services/finalizers 62 | - endpoints 63 | - persistentvolumeclaims 64 | - events 65 | - configmaps 66 | - secrets 67 | - namespaces 68 | - resourcequotas 69 | verbs: 70 | - '*' 71 | - apiGroups: 72 | - apps 73 | resources: 74 | - deployments 75 | - daemonsets 76 | - replicasets 77 | - statefulsets 78 | verbs: 79 | - '*' 80 | - apiGroups: 81 | - monitoring.coreos.com 82 | resources: 83 | - servicemonitors 84 | verbs: 85 | - get 86 | - create 87 | - apiGroups: 88 | - apps 89 | resourceNames: 90 | - sanbox-operator 91 | resources: 92 | - deployments/finalizers 93 | verbs: 94 | - update 95 | - apiGroups: 96 | - "" 97 | resources: 98 | - pods 99 | verbs: 100 | - get 101 | - apiGroups: 102 | - apps 103 | resources: 104 | - replicasets 105 | verbs: 106 | - get 107 | - apiGroups: 108 | - operators.plex.dev 109 | resources: 110 | - '*' 111 | verbs: 112 | - '*' 113 | - apiGroups: 114 | - rbac.authorization.k8s.io 115 | resources: 116 | - '*' 117 | verbs: 118 | - '*' 119 | --- 120 | apiVersion: rbac.authorization.k8s.io/v1 121 | kind: ClusterRole 122 | metadata: 123 | labels: 124 | app.kubernetes.io/name: sandbox-operator 125 | app.kubernetes.io/part-of: sandbox-operator 126 | name: sandbox-users 127 | rules: 128 | - apiGroups: 129 | - operators.plex.dev 130 | resources: 131 | - sandboxes 132 | verbs: 133 | - create 134 | - list 135 | - get 136 | --- 137 | apiVersion: rbac.authorization.k8s.io/v1 138 | kind: ClusterRoleBinding 139 | metadata: 140 | labels: 141 | app.kubernetes.io/name: sandbox-operator 142 | app.kubernetes.io/part-of: sandbox-operator 143 | name: sandbox-operator 144 | roleRef: 145 | apiGroup: rbac.authorization.k8s.io 146 | kind: ClusterRole 147 | name: sandbox-operator 148 | subjects: 149 | - kind: ServiceAccount 150 | name: sandbox-operator-sa 151 | namespace: default 152 | --- 153 | apiVersion: rbac.authorization.k8s.io/v1 154 | kind: ClusterRoleBinding 155 | metadata: 156 | labels: 157 | app.kubernetes.io/name: sandbox-operator 158 | app.kubernetes.io/part-of: sandbox-operator 159 | name: sandbox-user 160 | roleRef: 161 | apiGroup: rbac.authorization.k8s.io 162 | kind: ClusterRole 163 | name: sandbox-users 164 | subjects: 165 | - kind: Group 166 | name: system:authenticated 167 | --- 168 | apiVersion: apps/v1 169 | kind: Deployment 170 | metadata: 171 | labels: 172 | app.kubernetes.io/name: sandbox-operator 173 | app.kubernetes.io/version: v0.10.1 174 | name: sandbox-operator 175 | spec: 176 | selector: 177 | matchLabels: 178 | name: sandbox-operator 179 | template: 180 | metadata: 181 | labels: 182 | name: sandbox-operator 183 | spec: 184 | containers: 185 | - command: 186 | - sandbox-operator 187 | env: 188 | - name: OPERATOR_NAME 189 | value: sandbox-operator 190 | - name: WATCH_NAMESPACE 191 | valueFrom: 192 | fieldRef: 193 | fieldPath: metadata.namespace 194 | - name: POD_NAME 195 | valueFrom: 196 | fieldRef: 197 | fieldPath: metadata.name 198 | image: plexsystems/sandbox-operator:v0.10.1 199 | imagePullPolicy: IfNotPresent 200 | name: sandbox-operator 201 | serviceAccountName: sandbox-operator-sa 202 | -------------------------------------------------------------------------------- /controller/sandbox_test.go: -------------------------------------------------------------------------------- 1 | // +build !integration 2 | 3 | package controller 4 | 5 | import ( 6 | "context" 7 | "log" 8 | "testing" 9 | 10 | operatorsv1alpha1 "github.com/plexsystems/sandbox-operator/apis/operators/v1alpha1" 11 | 12 | corev1 "k8s.io/api/core/v1" 13 | rbacv1 "k8s.io/api/rbac/v1" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | "k8s.io/apimachinery/pkg/types" 16 | "k8s.io/client-go/kubernetes/scheme" 17 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 18 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 19 | ) 20 | 21 | func TestSandboxController_ByDefault_CreatesSandbox(t *testing.T) { 22 | ctx := context.TODO() 23 | 24 | s := scheme.Scheme 25 | s.AddKnownTypes(operatorsv1alpha1.SchemeGroupVersion, &operatorsv1alpha1.Sandbox{}) 26 | 27 | fakeClient := fake.NewFakeClientWithScheme(s) 28 | r := ReconcileSandbox{ 29 | client: fakeClient, 30 | scheme: s, 31 | subjectsClient: &DefaultSubjects{}, 32 | } 33 | 34 | sandbox := operatorsv1alpha1.Sandbox{ 35 | ObjectMeta: metav1.ObjectMeta{ 36 | Name: "test", 37 | }, 38 | } 39 | 40 | if err := r.client.Create(ctx, &sandbox); err != nil { 41 | t.Fatalf("create sandbox: %v", err) 42 | } 43 | 44 | request := reconcile.Request{ 45 | NamespacedName: types.NamespacedName{ 46 | Name: sandbox.Name, 47 | }, 48 | } 49 | 50 | if _, err := r.Reconcile(request); err != nil { 51 | log.Fatalf("reconcile sandbox: %v", err) 52 | } 53 | 54 | namespace := getNamespace(sandbox) 55 | if err := r.client.Get(ctx, types.NamespacedName{Name: namespace.Name}, &corev1.Namespace{}); err != nil { 56 | t.Errorf("expected Namespace to be created but it was not: %v", err) 57 | } 58 | 59 | role := getRole(sandbox) 60 | if err := r.client.Get(ctx, types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, &rbacv1.Role{}); err != nil { 61 | t.Errorf("expected Role to be created but it was not: %v", err) 62 | } 63 | 64 | roleBinding := getRoleBinding(sandbox) 65 | if err := r.client.Get(ctx, types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, &rbacv1.RoleBinding{}); err != nil { 66 | t.Errorf("expected RoleBinding to be created but it was not: %v", err) 67 | } 68 | 69 | clusterRole := getClusterRole(sandbox) 70 | if err := r.client.Get(ctx, types.NamespacedName{Name: clusterRole.Name}, &rbacv1.ClusterRole{}); err != nil { 71 | t.Errorf("expected ClusterRole to be created but it was not: %v", err) 72 | } 73 | 74 | clusterRoleBinding := getClusterRoleBinding(sandbox) 75 | if err := r.client.Get(ctx, types.NamespacedName{Name: clusterRoleBinding.Name}, &rbacv1.ClusterRoleBinding{}); err != nil { 76 | t.Errorf("expected ClusterRoleBinding to be created but it was not: %v", err) 77 | } 78 | 79 | resourceQuota := getResourceQuota(sandbox) 80 | if err := r.client.Get(ctx, types.NamespacedName{Name: resourceQuota.Name, Namespace: resourceQuota.Namespace}, &corev1.ResourceQuota{}); err != nil { 81 | t.Errorf("expected ResourceQuota to be created but it was not: %v", err) 82 | } 83 | } 84 | 85 | func TestSandboxController_AddOwner_UpdatesRoleAndClusterRoleBindings(t *testing.T) { 86 | ctx := context.TODO() 87 | 88 | s := scheme.Scheme 89 | s.AddKnownTypes(operatorsv1alpha1.SchemeGroupVersion, &operatorsv1alpha1.Sandbox{}) 90 | 91 | fakeClient := fake.NewFakeClientWithScheme(s) 92 | r := ReconcileSandbox{ 93 | client: fakeClient, 94 | scheme: s, 95 | subjectsClient: DefaultSubjects{}, 96 | } 97 | 98 | sandbox := operatorsv1alpha1.Sandbox{ 99 | ObjectMeta: metav1.ObjectMeta{ 100 | Name: "test", 101 | }, 102 | Spec: operatorsv1alpha1.SandboxSpec{ 103 | Owners: []string{"foo"}, 104 | }, 105 | } 106 | 107 | if err := r.client.Create(ctx, &sandbox); err != nil { 108 | t.Fatalf("create sandbox: %v", err) 109 | } 110 | 111 | request := reconcile.Request{ 112 | NamespacedName: types.NamespacedName{ 113 | Name: sandbox.Name, 114 | }, 115 | } 116 | 117 | if _, err := r.Reconcile(request); err != nil { 118 | log.Fatalf("reconcile sandbox: %v", err) 119 | } 120 | 121 | roleBinding := getRoleBinding(sandbox) 122 | clusterRoleBinding := getClusterRoleBinding(sandbox) 123 | 124 | var foundRoleBinding rbacv1.RoleBinding 125 | var foundClusterRoleBinding rbacv1.ClusterRoleBinding 126 | if err := r.client.Get(ctx, types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, &foundRoleBinding); err != nil { 127 | t.Fatalf("expected RoleBinding to exist but it does not: %v", err) 128 | } 129 | 130 | if err := r.client.Get(ctx, types.NamespacedName{Name: clusterRoleBinding.Name}, &foundClusterRoleBinding); err != nil { 131 | t.Fatalf("expected ClusterRoleBinding to exist but it does not: %v", err) 132 | } 133 | 134 | if foundRoleBinding.Subjects[0].Name != "foo" { 135 | t.Errorf("expected subject to be added to RoleBinding but it was not: %v", foundRoleBinding) 136 | } 137 | 138 | if foundClusterRoleBinding.Subjects[0].Name != "foo" { 139 | t.Errorf("expected subject to be added to ClusterRoleBinding but it was not: %v", foundClusterRoleBinding) 140 | } 141 | 142 | sandbox.Spec.Owners = []string{"foo", "bar"} 143 | 144 | if err := r.client.Update(ctx, &sandbox); err != nil { 145 | t.Fatalf("update sandbox: %v", err) 146 | } 147 | 148 | if _, err := r.Reconcile(request); err != nil { 149 | t.Fatalf("reconcile sandbox: %v", err) 150 | } 151 | 152 | if err := r.client.Get(ctx, types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, &foundRoleBinding); err != nil { 153 | t.Fatalf("expected RoleBinding to exist but it does not: %v", err) 154 | } 155 | 156 | if err := r.client.Get(ctx, types.NamespacedName{Name: clusterRoleBinding.Name}, &foundClusterRoleBinding); err != nil { 157 | t.Fatalf("expected ClusterRoleBinding to exist but it does not: %v", err) 158 | } 159 | 160 | if foundRoleBinding.Subjects[1].Name != "bar" { 161 | t.Errorf("expected subject to be added to RoleBinding but it was not: %v", foundRoleBinding) 162 | } 163 | 164 | if foundClusterRoleBinding.Subjects[1].Name != "bar" { 165 | t.Errorf("expected subject to be added to ClusterRoleBinding but it was not: %v", foundClusterRoleBinding) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plex Sandbox Operator 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/plexsystems/sandbox-operator)](https://goreportcard.com/report/github.com/plexsystems/sandbox-operator) 4 | [![GitHub release](https://img.shields.io/github/release/plexsystems/sandbox-operator.svg)](https://github.com/plexsystems/sandbox-operator/releases) 5 | 6 | ![sandbox-operator](img/sandbox-operator.png) 7 | 8 | ## Introduction 9 | 10 | The Plex Sandbox Operator is an operator for [Kubernetes](https://kubernetes.io/) that enables authenticated users to create their own isolated environments. 11 | 12 | ## Installation 13 | 14 | ### Kustomize 15 | 16 | This repository contains a [deploy](deploy) folder which contains all of the manifests required to deploy the operator, as well as a `kustomization.yaml` file. 17 | 18 | If you would like to apply your own customizations, reference the `deploy` folder and the version in your `kustomization.yaml`. 19 | 20 | #### Kustomize v2 21 | 22 | (version used in `kubectl apply -k .`) 23 | 24 | ```yaml 25 | bases: 26 | - git::https://github.com/plexsystems/sandbox-operator.git//deploy?ref=v0.10.1 27 | ``` 28 | 29 | #### Kustomize v3 30 | 31 | Latest version of Kustomize if installed as a standalone. Also version embedded in flux. 32 | 33 | ```yaml 34 | resources: 35 | - git::https://github.com/plexsystems/sandbox-operator.git//deploy?ref=v0.10.1 36 | ``` 37 | 38 | The [example](example) folder shows one example of how to customize the operator. 39 | 40 | ### Bundle 41 | 42 | A [bundle.yaml](bundle.yaml) is provided in the root of the repository which can then be applied via `kubectl apply`. 43 | 44 | ### Created ClusterRole and ClusterRoleBinding 45 | 46 | A `ClusterRole` resource and a `ClusterRoleBinding` resource will be created to enable authenticated users to create Sandbox resources. 47 | 48 | #### ClusterRole (sandbox-user) 49 | 50 | |Verbs|API Groups|Resources| 51 | |---|---|---| 52 | |create, list, get|operators.plex.dev|sandboxes| 53 | 54 | #### ClusterRoleBinding (sandbox-users) 55 | 56 | |API Group|Name|Subjects| 57 | |---|---|---| 58 | |rbac.authorization.k8s.io|sandbox-users|system:authenticated| 59 | 60 | ### Sandbox CRD 61 | 62 | A `CustomResourceDefinition` named `Sandbox` will be created. 63 | 64 | An example manifest for the Sandbox CRD is as follows: 65 | 66 | ```yaml 67 | apiVersion: operators.plex.dev/v1alpha1 68 | kind: Sandbox 69 | metadata: 70 | name: test 71 | spec: 72 | size: small 73 | owners: 74 | - foo@bar.com 75 | ``` 76 | 77 | ## Configuration 78 | 79 | ### Clients 80 | 81 | The Sandbox operator can leverage different clients, depending upon how authenitcation is configured for your cluster. 82 | 83 | #### Azure 84 | 85 | If Azure credentials are provided to the operators environment, it will perform a lookup of each user in the `owners` field and fetch that users `ObjectID` inside of Azure using the [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/api/resources/azure-ad-overview?view=graph-rest-1.0). 86 | 87 | This enables users to create Sandboxes with friendly names in the `owners` field (such as the owners email address) and have the operator itself handle the mapping to the `ObjectID` when creating the Kubernetes resources. 88 | 89 | To use the Azure client, include the following environment variables: 90 | 91 | - `AZURE_CLIENT_ID` 92 | - `AZURE_TENANT_ID` 93 | - `AZURE_CLIENT_SECRET` 94 | 95 | Your Azure Service Principal will need the following _Application_ permission for the **Azure Active Directory Graph API** (00000002-0000-0000-c000-000000000000): 96 | 97 | Directory.Read.All (5778995a-e1bf-45b8-affa-663a9f3f4d04) 98 | 99 | #### Default 100 | 101 | If no credentials are provided, the operator will create the `Role` and `ClusterRole` bindings using the values listed in the `owners` field. 102 | 103 | ### Docker Pull Secrets 104 | 105 | By default, the operator will not create any secrets in the provisioned namespace. 106 | 107 | **If the `PULL_SECRET_NAME` environment variable is set, the operator will copy your clusters pull secret to the provisioned namespace and patch the default service account.** 108 | 109 | `PULL_SECRET_NAME` should be the name of the pull secret that exists in your cluster. By default, the operator will look for your secret in the `default` namespace. 110 | 111 | To have the operator look in a different namespace for the pull secret, use the `PULL_SECRET_NAMESPACE` environment variable. 112 | 113 | ## Creating a Sandbox 114 | 115 | To create a Sandbox, apply a Sandbox CRD to the target cluster. 116 | 117 | The following will create a Sandbox called `foo` (the resulting namespace being `sandbox-foo`), and assign the RBAC policies to user `foo@bar.com`. 118 | 119 | ### foo.yaml 120 | 121 | ```yaml 122 | apiVersion: operators.plex.dev/v1alpha1 123 | kind: Sandbox 124 | metadata: 125 | name: foo 126 | spec: 127 | size: small 128 | owners: 129 | - foo@bar.com 130 | ``` 131 | 132 | ```console 133 | $ kubectl apply -f foo.yaml 134 | sandboxes.operators.plex.dev "foo" created 135 | ``` 136 | 137 | ## Created Resources 138 | 139 | Assuming the name of the created Sandbox is named `foo`, the following resources will be created per Sandbox: 140 | 141 | ### Namespace (sandbox-foo) 142 | 143 | ### ClusterRole (sandbox-foo-admin) 144 | 145 | |Verbs|API Groups|Resources|ResourceNames| 146 | |---|---|---|---| 147 | |*|operators.plex.dev|sandboxes|sandbox-foo| 148 | 149 | This is created so that only users defined in the `owners` field can delete their Sandboxes. 150 | 151 | ### ClusterRoleBinding (sandbox-foo-admins) 152 | 153 | One `ClusterRoleBinding` per name in the `owners` field. Bindings are added and removed as users are added and removed from the `owners` field. 154 | 155 | ### Role (sandbox-foo-owner) 156 | 157 | |Verbs|API Groups|Resources| 158 | |---|---|---| 159 | |*|core|pods, pods/log, pods/portforward, services, services/finalizers, endpoints, persistentvolumeclaims, events, configmaps, replicationcontrollers| 160 | |create|core|secrets| 161 | |*|apps, extensions|deployments, daemonsets, replicasets, statefulsets| 162 | |*|autoscaling|horizontalpodautoscalers| 163 | |*|batch|jobs, cronjobs| 164 | |create, list, get|rbac.authorization.k8s.io|roles, rolebindings| 165 | 166 | ### RoleBinding (sandbox-foo-owners) 167 | 168 | One `RoleBinding` per name in the `owners` field. Bindings are added and removed as users are added and removed from the `owners` field. 169 | 170 | ### ResourceQuota (sandbox-foo-resourcequota) 171 | 172 | The `ResourceQuota` that is applied to the `Namespace` depends on the `size` of the `Sandbox` that was created. Defaults to `small` if no size is given. 173 | 174 | #### Small 175 | 176 | |Resource Name|Quantity| 177 | |---|---| 178 | |ResourceRequestsCPU|0.25| 179 | |ResourceLimitsCPU|0.5| 180 | |ResourceRequestsMemory|250Mi| 181 | |ResourceLimitsMemory|500Mi| 182 | |ResourceRequestsStorage|10Gi| 183 | |ResourcePersistentVolumeClaims|2| 184 | 185 | #### Large 186 | 187 | |Resource Name|Quantity| 188 | |---|---| 189 | |ResourceRequestsCPU|1| 190 | |ResourceLimitsCPU|2| 191 | |ResourceRequestsMemory|2Gi| 192 | |ResourceLimitsMemory|8Gi| 193 | |ResourceRequestsStorage|40Gi| 194 | |ResourcePersistentVolumeClaims|8| 195 | 196 | ## Managing Owners of a Sandbox 197 | 198 | After the Sandbox has been created, you can add or remove owners that are associated to it. 199 | 200 | For example, to add `more@bar.com` as an owner, add their name to the list of owners and apply the changes: 201 | 202 | ```yaml 203 | apiVersion: operators.plex.dev/v1alpha1 204 | kind: Sandbox 205 | metadata: 206 | name: foo 207 | spec: 208 | size: small 209 | owners: 210 | - foo@bar.com 211 | - more@bar.com 212 | ``` 213 | 214 | ```console 215 | $ kubectl apply -f sandbox-foo.yaml 216 | sandboxes.operators.plex.dev "foo" configured 217 | ``` 218 | 219 | This will cause the operator to add `ClusterRoleBinding` and `RoleBinding` resources to match the owners list. 220 | 221 | When `owners` are removed from the Sandbox, their `ClusterRoleBinding` and `RoleBinding` will also be removed. 222 | 223 | ## Deleting a Sandbox 224 | 225 | To delete a Sandbox, delete the Sandbox resource from the cluster: 226 | 227 | ```console 228 | $ kubectl delete sandbox foo 229 | sandboxes.operators.plex.dev "foo" deleted 230 | ``` 231 | 232 | Deleting a Sandbox will delete the `Namespace` as well as the `ClusterRole` and `ClusterRoleBinding` resources. 233 | 234 | ## Metrics 235 | 236 | The operator exposes two metric ports for the `/metrics` endpoint: 237 | 238 | - Port `8383` exposes metrics for the operator itself 239 | - Port `8686` exposes metrics for the `Sandbox` CRD 240 | 241 | Additionally, if [prometheus-operator](https://github.com/coreos/prometheus-operator) is installed into the cluster, a `ServiceMonitor` is created for the operator. 242 | 243 | ## Development 244 | 245 | No external tooling is required to develop and build the operator. However, some tooling is required to run the integration tests: 246 | 247 | - [Kind](https://github.com/kubernetes-sigs/kind) 248 | - [Kustomize](https://github.com/kubernetes-sigs/kustomize) 249 | 250 | ## Testing 251 | 252 | The provided `Makefile` contains commands that assist with running the tests for the operator. 253 | 254 | ### Unit tests 255 | 256 | `make test-unit` will use an in-memory kubernetes client to validate and test your changes without the need for an external Kubernetes cluster. 257 | 258 | ### Integration tests 259 | 260 | `make test-integration` will create a Kubernetes cluster for you, using Kind, and deploy the operator to it. The integration tests will then be ran against the newly created cluster. 261 | 262 | #### Testing different Kubernetes versions 263 | 264 | To test the operator with different versions of Kubernetes, you can use the `KUBERNETES_VERSION` variable when calling `make`. 265 | 266 | For example, to test on Kubernetes v1.16.3, run the following command: 267 | 268 | `make test-integration KUBERNETES_VERSION=v1.16.3` 269 | 270 | ## Contributing 271 | 272 | We :heart: pull requests. If you have a question, feedback, or would like to contribute — please feel free to create an issue or open a pull request! 273 | -------------------------------------------------------------------------------- /controller/sandbox.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strings" 10 | 11 | operatorsv1alpha1 "github.com/plexsystems/sandbox-operator/apis/operators/v1alpha1" 12 | 13 | corev1 "k8s.io/api/core/v1" 14 | rbacv1 "k8s.io/api/rbac/v1" 15 | "k8s.io/apimachinery/pkg/api/errors" 16 | "k8s.io/apimachinery/pkg/api/resource" 17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 | "k8s.io/apimachinery/pkg/runtime" 19 | "k8s.io/apimachinery/pkg/types" 20 | ctrl "sigs.k8s.io/controller-runtime" 21 | "sigs.k8s.io/controller-runtime/pkg/client" 22 | "sigs.k8s.io/controller-runtime/pkg/client/config" 23 | "sigs.k8s.io/controller-runtime/pkg/controller" 24 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 25 | "sigs.k8s.io/controller-runtime/pkg/handler" 26 | "sigs.k8s.io/controller-runtime/pkg/manager" 27 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 28 | "sigs.k8s.io/controller-runtime/pkg/source" 29 | ) 30 | 31 | var _ reconcile.Reconciler = &ReconcileSandbox{} 32 | 33 | // SubjectsClient defines a client that gets subjects 34 | type SubjectsClient interface { 35 | Subjects(ctx context.Context, users []string) ([]rbacv1.Subject, error) 36 | } 37 | 38 | // ReconcileSandbox reconciles a Sandbox object 39 | type ReconcileSandbox struct { 40 | client client.Client 41 | scheme *runtime.Scheme 42 | subjectsClient SubjectsClient 43 | } 44 | 45 | // NewReconcileSandbox creates a new reconciler for Sandbox resources 46 | func NewReconcileSandbox(scheme *runtime.Scheme) (*ReconcileSandbox, error) { 47 | client, err := NewClient(scheme) 48 | if err != nil { 49 | return nil, fmt.Errorf("new client: %w", err) 50 | } 51 | 52 | subjects, err := newSubjectsClient() 53 | if err != nil { 54 | return nil, fmt.Errorf("new subjects: %w", err) 55 | } 56 | 57 | reconcileSandbox := ReconcileSandbox{ 58 | client: client, 59 | scheme: scheme, 60 | subjectsClient: subjects, 61 | } 62 | 63 | return &reconcileSandbox, nil 64 | } 65 | 66 | // NewClient creates a new kubernetes client 67 | func NewClient(scheme *runtime.Scheme) (client.Client, error) { 68 | config, err := config.GetConfig() 69 | if err != nil { 70 | return nil, fmt.Errorf("get config: %w", err) 71 | } 72 | 73 | client, err := client.New(config, client.Options{Scheme: scheme}) 74 | if err != nil { 75 | return nil, fmt.Errorf("create client: %w", err) 76 | } 77 | 78 | return client, nil 79 | } 80 | 81 | // Add creates a new Sandbox controller and adds it to the controller manager 82 | func Add(mgr manager.Manager) error { 83 | reconcileSandbox, err := NewReconcileSandbox(mgr.GetScheme()) 84 | if err != nil { 85 | return fmt.Errorf("new reconciler: %w", err) 86 | } 87 | 88 | c, err := controller.New("sandbox-controller", mgr, controller.Options{Reconciler: reconcileSandbox}) 89 | if err != nil { 90 | return fmt.Errorf("new controller: %w", err) 91 | } 92 | 93 | if err := c.Watch(&source.Kind{Type: &operatorsv1alpha1.Sandbox{}}, &handler.EnqueueRequestForObject{}); err != nil { 94 | return fmt.Errorf("watch Sandbox: %w", err) 95 | } 96 | 97 | return nil 98 | } 99 | 100 | // Reconcile syncs Sandbox changes to the cluster 101 | func (r *ReconcileSandbox) Reconcile(request reconcile.Request) (reconcile.Result, error) { 102 | ctx := context.Background() 103 | 104 | if err := r.handleReconcile(ctx, request); err != nil { 105 | log.Printf("reconcile Sandbox: %v\n", err) 106 | return reconcile.Result{}, err 107 | } 108 | 109 | return reconcile.Result{}, nil 110 | } 111 | 112 | func (r *ReconcileSandbox) handleReconcile(ctx context.Context, request reconcile.Request) error { 113 | var sandbox operatorsv1alpha1.Sandbox 114 | if err := r.client.Get(ctx, request.NamespacedName, &sandbox); err != nil { 115 | if errors.IsNotFound(err) { 116 | return nil 117 | } 118 | 119 | return fmt.Errorf("get Sandbox: %w", err) 120 | } 121 | 122 | namespace := getNamespace(sandbox) 123 | _, err := ctrl.CreateOrUpdate(ctx, r.client, &namespace, func() error { 124 | return controllerutil.SetControllerReference(&sandbox, &namespace, r.scheme) 125 | }) 126 | if err != nil { 127 | return fmt.Errorf("reconcile Namespace: %w", err) 128 | } 129 | 130 | resourceQuota := getResourceQuota(sandbox) 131 | _, err = ctrl.CreateOrUpdate(ctx, r.client, &resourceQuota, func() error { 132 | return controllerutil.SetControllerReference(&sandbox, &resourceQuota, r.scheme) 133 | }) 134 | if err != nil { 135 | return fmt.Errorf("reconcile ResourceQuota: %w", err) 136 | } 137 | 138 | role := getRole(sandbox) 139 | _, err = ctrl.CreateOrUpdate(ctx, r.client, &role, func() error { 140 | return controllerutil.SetControllerReference(&sandbox, &role, r.scheme) 141 | }) 142 | if err != nil { 143 | return fmt.Errorf("reconcile Role: %w", err) 144 | } 145 | 146 | roleBinding := getRoleBinding(sandbox) 147 | _, err = ctrl.CreateOrUpdate(ctx, r.client, &roleBinding, func() error { 148 | subjects, err := r.subjectsClient.Subjects(ctx, sandbox.Spec.Owners) 149 | if err != nil { 150 | return fmt.Errorf("get subjects: %w", err) 151 | } 152 | 153 | roleBinding.Subjects = subjects 154 | return controllerutil.SetControllerReference(&sandbox, &roleBinding, r.scheme) 155 | }) 156 | if err != nil { 157 | return fmt.Errorf("reconcile RoleBinding: %w", err) 158 | } 159 | 160 | clusterRole := getClusterRole(sandbox) 161 | _, err = ctrl.CreateOrUpdate(ctx, r.client, &clusterRole, func() error { 162 | return controllerutil.SetControllerReference(&sandbox, &clusterRole, r.scheme) 163 | }) 164 | if err != nil { 165 | return fmt.Errorf("reconcile ClusterRole: %w", err) 166 | } 167 | 168 | clusterRoleBinding := getClusterRoleBinding(sandbox) 169 | _, err = ctrl.CreateOrUpdate(ctx, r.client, &clusterRoleBinding, func() error { 170 | subjects, err := r.subjectsClient.Subjects(ctx, sandbox.Spec.Owners) 171 | if err != nil { 172 | return fmt.Errorf("get subjects: %w", err) 173 | } 174 | 175 | clusterRoleBinding.Subjects = subjects 176 | return controllerutil.SetControllerReference(&sandbox, &clusterRoleBinding, r.scheme) 177 | }) 178 | if err != nil { 179 | return fmt.Errorf("reconcile ClusterRoleBinding: %w", err) 180 | } 181 | 182 | if os.Getenv("PULL_SECRET_NAME") != "" { 183 | secretName := os.Getenv("PULL_SECRET_NAME") 184 | secretData, err := getDockerSecretData(ctx, r.client, secretName) 185 | if err != nil { 186 | return fmt.Errorf("get secret data: %w", err) 187 | } 188 | 189 | secret := getDockerSecret(sandbox, secretName, secretData) 190 | _, err = ctrl.CreateOrUpdate(ctx, r.client, &secret, func() error { 191 | return controllerutil.SetControllerReference(&sandbox, &secret, r.scheme) 192 | }) 193 | if err != nil { 194 | return fmt.Errorf("reconcile docker Secret: %w", err) 195 | } 196 | 197 | var defaultServiceAccount corev1.ServiceAccount 198 | if err := r.client.Get(ctx, types.NamespacedName{Name: "default", Namespace: namespace.Name}, &defaultServiceAccount); err != nil { 199 | return fmt.Errorf("get default service account: %w", err) 200 | } 201 | 202 | patchBytes, err := getPatchBytes(secretName) 203 | if err != nil { 204 | return fmt.Errorf("get patch bytes: %w", err) 205 | } 206 | 207 | patch := client.ConstantPatch(types.StrategicMergePatchType, patchBytes) 208 | if err := r.client.Patch(ctx, &defaultServiceAccount, patch, &client.PatchOptions{}); err != nil { 209 | return fmt.Errorf("patch service account: %w", err) 210 | } 211 | } 212 | 213 | return nil 214 | } 215 | 216 | func getPatchBytes(secretName string) ([]byte, error) { 217 | type imagePullSecretsPatch struct { 218 | ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` 219 | } 220 | 221 | patch := imagePullSecretsPatch{ 222 | ImagePullSecrets: []corev1.LocalObjectReference{ 223 | {Name: secretName}, 224 | }, 225 | } 226 | 227 | patchString, err := json.Marshal(patch) 228 | if err != nil { 229 | return nil, fmt.Errorf("patching default service account: %w", err) 230 | } 231 | 232 | return patchString, nil 233 | } 234 | 235 | func getNamespace(sandbox operatorsv1alpha1.Sandbox) corev1.Namespace { 236 | namespace := corev1.Namespace{ 237 | ObjectMeta: metav1.ObjectMeta{ 238 | Name: "sandbox-" + sandbox.Name, 239 | Labels: getCommonLabels(), 240 | }, 241 | } 242 | 243 | return namespace 244 | } 245 | 246 | func getRole(sandbox operatorsv1alpha1.Sandbox) rbacv1.Role { 247 | role := rbacv1.Role{ 248 | ObjectMeta: metav1.ObjectMeta{ 249 | Name: "sandbox-" + sandbox.Name + "-owner", 250 | Namespace: "sandbox-" + sandbox.Name, 251 | Labels: getCommonLabels(), 252 | }, 253 | Rules: []rbacv1.PolicyRule{ 254 | { 255 | Verbs: []string{"*"}, 256 | APIGroups: []string{""}, 257 | Resources: []string{ 258 | "pods", 259 | "pods/log", 260 | "pods/portforward", 261 | "services", 262 | "services/finalizers", 263 | "endpoints", 264 | "persistentvolumeclaims", 265 | "events", 266 | "configmaps", 267 | "replicationcontrollers", 268 | }, 269 | }, 270 | { 271 | Verbs: []string{"*"}, 272 | APIGroups: []string{ 273 | "apps", 274 | "extensions", 275 | }, 276 | Resources: []string{ 277 | "deployments", 278 | "daemonsets", 279 | "replicasets", 280 | "statefulsets", 281 | }, 282 | }, 283 | { 284 | Verbs: []string{"*"}, 285 | APIGroups: []string{"autoscaling"}, 286 | Resources: []string{"horizontalpodautoscalers"}, 287 | }, 288 | { 289 | Verbs: []string{"*"}, 290 | APIGroups: []string{"batch"}, 291 | Resources: []string{ 292 | "jobs", 293 | "cronjobs", 294 | }, 295 | }, 296 | { 297 | Verbs: []string{ 298 | "create", 299 | "list", 300 | "get", 301 | }, 302 | APIGroups: []string{"rbac.authorization.k8s.io"}, 303 | Resources: []string{ 304 | "roles", 305 | "rolebindings", 306 | }, 307 | }, 308 | { 309 | Verbs: []string{ 310 | "create", 311 | "delete", 312 | }, 313 | APIGroups: []string{""}, 314 | Resources: []string{ 315 | "secrets", 316 | }, 317 | }, 318 | }, 319 | } 320 | 321 | return role 322 | } 323 | 324 | func getRoleBinding(sandbox operatorsv1alpha1.Sandbox) rbacv1.RoleBinding { 325 | roleBinding := rbacv1.RoleBinding{ 326 | ObjectMeta: metav1.ObjectMeta{ 327 | Name: "sandbox-" + sandbox.Name + "-owners", 328 | Namespace: "sandbox-" + sandbox.Name, 329 | Labels: getCommonLabels(), 330 | }, 331 | RoleRef: rbacv1.RoleRef{ 332 | APIGroup: "rbac.authorization.k8s.io", 333 | Kind: "Role", 334 | Name: "sandbox-" + sandbox.Name + "-owner", 335 | }, 336 | } 337 | 338 | return roleBinding 339 | } 340 | 341 | func getClusterRole(sandbox operatorsv1alpha1.Sandbox) rbacv1.ClusterRole { 342 | clusterRole := rbacv1.ClusterRole{ 343 | ObjectMeta: metav1.ObjectMeta{ 344 | Name: "sandbox-" + sandbox.Name + "-admin", 345 | Labels: getCommonLabels(), 346 | }, 347 | Rules: []rbacv1.PolicyRule{ 348 | { 349 | Verbs: []string{"*"}, 350 | APIGroups: []string{"operators.plex.dev"}, 351 | Resources: []string{"sandboxes"}, 352 | ResourceNames: []string{sandbox.Name}, 353 | }, 354 | }, 355 | } 356 | 357 | return clusterRole 358 | } 359 | 360 | func getClusterRoleBinding(sandbox operatorsv1alpha1.Sandbox) rbacv1.ClusterRoleBinding { 361 | clusterRoleBinding := rbacv1.ClusterRoleBinding{ 362 | ObjectMeta: metav1.ObjectMeta{ 363 | Name: "sandbox-" + sandbox.Name + "-admins", 364 | Labels: getCommonLabels(), 365 | }, 366 | RoleRef: rbacv1.RoleRef{ 367 | APIGroup: "rbac.authorization.k8s.io", 368 | Kind: "ClusterRole", 369 | Name: "sandbox-" + sandbox.Name + "-admin", 370 | }, 371 | } 372 | 373 | return clusterRoleBinding 374 | } 375 | 376 | func getResourceQuota(sandbox operatorsv1alpha1.Sandbox) corev1.ResourceQuota { 377 | var resourceQuotaSpec corev1.ResourceQuotaSpec 378 | if strings.EqualFold(sandbox.Spec.Size, "large") { 379 | resourceQuotaSpec = getLargeResourceQuotaSpec() 380 | } else { 381 | resourceQuotaSpec = getSmallResourceQuotaSpec() 382 | } 383 | 384 | resourceQuota := corev1.ResourceQuota{ 385 | ObjectMeta: metav1.ObjectMeta{ 386 | Name: "sandbox-" + sandbox.Name + "-resourcequota", 387 | Namespace: "sandbox-" + sandbox.Name, 388 | Labels: getCommonLabels(), 389 | }, 390 | Spec: resourceQuotaSpec, 391 | } 392 | 393 | return resourceQuota 394 | } 395 | 396 | func getLargeResourceQuotaSpec() corev1.ResourceQuotaSpec { 397 | resourceQuotaSpec := corev1.ResourceQuotaSpec{ 398 | Hard: corev1.ResourceList{ 399 | corev1.ResourceRequestsCPU: resource.MustParse("1"), 400 | corev1.ResourceLimitsCPU: resource.MustParse("2"), 401 | corev1.ResourceRequestsMemory: resource.MustParse("2Gi"), 402 | corev1.ResourceLimitsMemory: resource.MustParse("8Gi"), 403 | corev1.ResourceRequestsStorage: resource.MustParse("40Gi"), 404 | corev1.ResourcePersistentVolumeClaims: resource.MustParse("8"), 405 | }, 406 | } 407 | 408 | return resourceQuotaSpec 409 | } 410 | 411 | func getSmallResourceQuotaSpec() corev1.ResourceQuotaSpec { 412 | resourceQuotaSpec := corev1.ResourceQuotaSpec{ 413 | Hard: corev1.ResourceList{ 414 | corev1.ResourceRequestsCPU: resource.MustParse("0.25"), 415 | corev1.ResourceLimitsCPU: resource.MustParse("0.5"), 416 | corev1.ResourceRequestsMemory: resource.MustParse("250Mi"), 417 | corev1.ResourceLimitsMemory: resource.MustParse("500Mi"), 418 | corev1.ResourceRequestsStorage: resource.MustParse("10Gi"), 419 | corev1.ResourcePersistentVolumeClaims: resource.MustParse("2"), 420 | }, 421 | } 422 | 423 | return resourceQuotaSpec 424 | } 425 | 426 | func getDockerSecretData(ctx context.Context, client client.Client, secretName string) ([]byte, error) { 427 | var secretNamespace string 428 | if os.Getenv("PULL_SECRET_NAMESPACE") != "" { 429 | secretNamespace = os.Getenv("PULL_SECRET_NAMESPACE") 430 | } else { 431 | secretNamespace = "default" 432 | } 433 | 434 | var dockerSecret corev1.Secret 435 | if err := client.Get(ctx, types.NamespacedName{Name: secretName, Namespace: secretNamespace}, &dockerSecret); err != nil { 436 | return nil, fmt.Errorf("get docker secret: %w", err) 437 | } 438 | 439 | if _, ok := dockerSecret.Data[corev1.DockerConfigJsonKey]; !ok { 440 | return nil, fmt.Errorf("secret missing dockerconfig data") 441 | } 442 | 443 | return dockerSecret.Data[corev1.DockerConfigJsonKey], nil 444 | } 445 | 446 | func getDockerSecret(sandbox operatorsv1alpha1.Sandbox, secretName string, secretData []byte) corev1.Secret { 447 | dockerSecret := corev1.Secret{ 448 | ObjectMeta: metav1.ObjectMeta{ 449 | Name: secretName, 450 | Namespace: "sandbox-" + sandbox.Name, 451 | }, 452 | Data: map[string][]byte{ 453 | corev1.DockerConfigJsonKey: []byte(secretData), 454 | }, 455 | Type: corev1.SecretTypeDockerConfigJson, 456 | } 457 | 458 | return dockerSecret 459 | } 460 | 461 | func getCommonLabels() map[string]string { 462 | commonLabels := make(map[string]string) 463 | commonLabels["app.kubernetes.io/name"] = "sandbox-operator" 464 | commonLabels["app.kubernetes.io/part-of"] = "sandbox-operator" 465 | 466 | return commonLabels 467 | } 468 | 469 | // DefaultSubjects represents default subjects 470 | type DefaultSubjects struct{} 471 | 472 | // Subjects returns the default subjects from a given list of users 473 | func (DefaultSubjects) Subjects(ctx context.Context, users []string) ([]rbacv1.Subject, error) { 474 | return getSubjects(users), nil 475 | } 476 | 477 | func getSubjects(users []string) []rbacv1.Subject { 478 | var subjects []rbacv1.Subject 479 | for _, user := range users { 480 | subject := rbacv1.Subject{ 481 | APIGroup: "rbac.authorization.k8s.io", 482 | Kind: "User", 483 | Name: user, 484 | } 485 | 486 | subjects = append(subjects, subject) 487 | } 488 | 489 | return subjects 490 | } 491 | 492 | func newSubjectsClient() (SubjectsClient, error) { 493 | if os.Getenv("AZURE_TENANT_ID") == "" { 494 | return DefaultSubjects{}, nil 495 | } 496 | 497 | azureSubjects, err := NewAzureSubjectsClient() 498 | if err != nil { 499 | return nil, fmt.Errorf("new azure subjects: %w", err) 500 | } 501 | 502 | return azureSubjects, nil 503 | } 504 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | bitbucket.org/ww/goautoneg v0.0.0-20120707110453-75cd24fc2f2c/go.mod h1:1vhO7Mn/FZMgOgDVGLy5X1mE6rq1HbkBdkF/yj8zkcg= 2 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 4 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 5 | cloud.google.com/go v0.37.2 h1:4y4L7BdHenTfZL0HervofNTHh9Ad6mNX72cQvl+5eH0= 6 | cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= 7 | contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc= 8 | contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= 9 | git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= 10 | git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= 11 | github.com/Azure/azure-sdk-for-go v32.5.0+incompatible h1:Hn/DsObfmw0M7dMGS/c0MlVrJuGFzHzOpBWL89acR68= 12 | github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 13 | github.com/Azure/azure-sdk-for-go v39.1.0+incompatible h1:ZwZgxG+8jLMJbY2NN1e8wiqE+/j4GiuHEc7U5wvPR94= 14 | github.com/Azure/azure-sdk-for-go v39.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 15 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 16 | github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtzYCrT+um/rrSQ1BfA= 17 | github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 18 | github.com/Azure/go-autorest v11.7.0+incompatible h1:gzma19dc9ejB75D90E5S+/wXouzpZyA+CV+/MJPSD/k= 19 | github.com/Azure/go-autorest v11.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 20 | github.com/Azure/go-autorest v13.3.3+incompatible h1:oYzB8/Ldlo1Bq7By79KO/1nxWuoLnEoGQiToUM2rBZo= 21 | github.com/Azure/go-autorest v13.3.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 22 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 23 | github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= 24 | github.com/Azure/go-autorest/autorest v0.9.5 h1:IvOB+EPvwfzDNJBPe1i3wtnNKl1d/LJ+tweb0N1H3hg= 25 | github.com/Azure/go-autorest/autorest v0.9.5/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= 26 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 27 | github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= 28 | github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= 29 | github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= 30 | github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= 31 | github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk= 32 | github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= 33 | github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U= 34 | github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= 35 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 36 | github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= 37 | github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= 38 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 39 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 40 | github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= 41 | github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= 42 | github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= 43 | github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= 44 | github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= 45 | github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= 46 | github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= 47 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 48 | github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= 49 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 50 | github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 51 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 52 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 53 | github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 54 | github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= 55 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 56 | github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 57 | github.com/Masterminds/sprig v0.0.0-20190301161902-9f8fceff796f/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= 58 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 59 | github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 60 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 61 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 62 | github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= 63 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 64 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 65 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= 66 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 67 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 68 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 69 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 70 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 71 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 72 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 73 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 74 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 75 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 76 | github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= 77 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 78 | github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 79 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 80 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= 81 | github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= 82 | github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4= 83 | github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 84 | github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= 85 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 86 | github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 87 | github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 88 | github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 89 | github.com/coreos/etcd v3.3.9+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 90 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 91 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 92 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 93 | github.com/coreos/go-oidc v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 94 | github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 95 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 96 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 97 | github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 98 | github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 99 | github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 100 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 101 | github.com/coreos/prometheus-operator v0.29.0 h1:Moi4klbr1xUVaofWzlaM12mxwCL294GiLW2Qj8ku0sY= 102 | github.com/coreos/prometheus-operator v0.29.0/go.mod h1:SO+r5yZUacDFPKHfPoUjI3hMsH+ZUdiuNNhuSq3WoSg= 103 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 104 | github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= 105 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 106 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 107 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 108 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 109 | github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 110 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 111 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 112 | github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= 113 | github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= 114 | github.com/docker/distribution v2.6.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 115 | github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 116 | github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 117 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 118 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 119 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= 120 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 121 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 122 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 123 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 124 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 125 | github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= 126 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 127 | github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 128 | github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 129 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 130 | github.com/emicklei/go-restful v2.8.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 131 | github.com/emicklei/go-restful v2.8.1+incompatible h1:AyDqLHbJ1quqbWr/OWDw+PlIP8ZFoTmYrGYaxzrLbNg= 132 | github.com/emicklei/go-restful v2.8.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 133 | github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6/go.mod h1:qr0VowGBT4CS4Q8vFF8BSeKz34PuqKGxs/L0IAQA9DQ= 134 | github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 135 | github.com/evanphx/json-patch v3.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 136 | github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 137 | github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= 138 | github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 139 | github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= 140 | github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= 141 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 142 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 143 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 144 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 145 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 146 | github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 147 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 148 | github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 149 | github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 150 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 151 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 152 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 153 | github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= 154 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 155 | github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= 156 | github.com/go-logr/zapr v0.1.1 h1:qXBXPDdNncunGs7XeEpsJt8wCjYBygluzfdLO0G5baE= 157 | github.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= 158 | github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= 159 | github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 160 | github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 161 | github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 162 | github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 163 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 164 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 165 | github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 166 | github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0= 167 | github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 168 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 169 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 170 | github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 171 | github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= 172 | github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= 173 | github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 174 | github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 175 | github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= 176 | github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q= 177 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 178 | github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 179 | github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 180 | github.com/go-openapi/spec v0.19.2 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE= 181 | github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= 182 | github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 183 | github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 184 | github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 185 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 186 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 187 | github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 188 | github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 189 | github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE= 190 | github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 191 | github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 192 | github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 193 | github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 194 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 195 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 196 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 197 | github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= 198 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 199 | github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= 200 | github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= 201 | github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= 202 | github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= 203 | github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= 204 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 205 | github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 206 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 207 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 208 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 209 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 210 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 211 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 212 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 213 | github.com/golang/groupcache v0.0.0-20180924190550-6f2cf27854a4/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 214 | github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 215 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= 216 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 217 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 218 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 219 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 220 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 221 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 222 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 223 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 224 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 225 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 226 | github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 227 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 228 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 229 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 230 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 231 | github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 232 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 233 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 234 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 235 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 236 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 237 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 238 | github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= 239 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 240 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 241 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 242 | github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= 243 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 244 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 245 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 246 | github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= 247 | github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= 248 | github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= 249 | github.com/gophercloud/gophercloud v0.2.0 h1:lD2Bce2xBAMNNcFZ0dObTpXkGLlVIb33RPVUNVpw6ic= 250 | github.com/gophercloud/gophercloud v0.2.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= 251 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 252 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 253 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 254 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 255 | github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 256 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 257 | github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 258 | github.com/gregjones/httpcache v0.0.0-20190203031600-7a902570cb17/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 259 | github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 260 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 261 | github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 262 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 263 | github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 264 | github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 265 | github.com/grpc-ecosystem/grpc-gateway v1.5.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 266 | github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 267 | github.com/grpc-ecosystem/grpc-gateway v1.6.3/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 268 | github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE= 269 | github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 270 | github.com/grpc-ecosystem/grpc-health-probe v0.2.0/go.mod h1:4GVx/bTCtZaSzhjbGueDY5YgBdsmKeVx+LErv/n0L6s= 271 | github.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db/go.mod h1:uBKkC2RbarFsvS5jMJHpVhTLvGlGQj9JJwkaePE3FWI= 272 | github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 273 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 274 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 275 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 276 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 277 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 278 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 279 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 280 | github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= 281 | github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= 282 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 283 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 284 | github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= 285 | github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 286 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 287 | github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= 288 | github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= 289 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 290 | github.com/jonboulle/clockwork v0.0.0-20141017032234-72f9bd7c4e0c/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 291 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 292 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 293 | github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 294 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 295 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= 296 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 297 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 298 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 299 | github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= 300 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 301 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 302 | github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 303 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 304 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 305 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 306 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 307 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 308 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 309 | github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 310 | github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 311 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 312 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 313 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 314 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 315 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 316 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 317 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 318 | github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 319 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4= 320 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 321 | github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= 322 | github.com/martinlindhe/base36 v0.0.0-20180729042928-5cda0030da17/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8= 323 | github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= 324 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 325 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 326 | github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 327 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 328 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 329 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 330 | github.com/maxbrunsfeld/counterfeiter v0.0.0-20181017030959-1aadac120687/go.mod h1:aoVsckWnsNzazwF2kmD+bzgdr4GBlbK91zsdivQJ2eU= 331 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 332 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 333 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 334 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 335 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 336 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 337 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 338 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 339 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 340 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 341 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 342 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 343 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 344 | github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= 345 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 346 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 347 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 348 | github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= 349 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 350 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 351 | github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 352 | github.com/onsi/gomega v1.4.2-0.20180831124310-ae19f1b56d53/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 353 | github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 354 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 355 | github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= 356 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 357 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 358 | github.com/openshift/origin v0.0.0-20160503220234-8f127d736703/go.mod h1:0Rox5r9C8aQn6j1oAOQ0c1uC86mYbUFObzjBRvUKHII= 359 | github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= 360 | github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= 361 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 362 | github.com/operator-framework/go-appr v0.0.0-20180917210448-f2aef88446f2/go.mod h1:YNzwUx1i6C4dXWcffyq3yaIb0rh/K8/OvQ4vG0SNlSw= 363 | github.com/operator-framework/operator-lifecycle-manager v0.0.0-20181023032605-e838f7fb2186/go.mod h1:Ma5ZXd4S1vmMyewWlF7aO8CZiokR7Sd8dhSfkGkNU4U= 364 | github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190105193533-81104ffdc4fb/go.mod h1:XMyE4n2opUK4N6L45YGQkXXi8F9fD7XDYFv/CsS6V5I= 365 | github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a/go.mod h1:vq6TTFvg6ti1Bn6ACsZneZTmjTsURgDD6tQtVDbEgsU= 366 | github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190128024246-5eb7ae5bdb7a/go.mod h1:vq6TTFvg6ti1Bn6ACsZneZTmjTsURgDD6tQtVDbEgsU= 367 | github.com/operator-framework/operator-marketplace v0.0.0-20190216021216-57300a3ef3ba/go.mod h1:msZSL8pXwzQjB+hU+awVrZQw94IwJi3sNZVD3NoESIs= 368 | github.com/operator-framework/operator-registry v1.0.1/go.mod h1:1xEdZjjUg2hPEd52LG3YQ0jtwiwEGdm98S1TH5P4RAA= 369 | github.com/operator-framework/operator-registry v1.0.4/go.mod h1:hve6YwcjM2nGVlscLtNsp9sIIBkNZo6jlJgzWw7vP9s= 370 | github.com/operator-framework/operator-registry v1.1.1/go.mod h1:7D4WEwL+EKti5npUh4/u64DQhawCBRugp8Ql20duUb4= 371 | github.com/operator-framework/operator-sdk v0.11.0 h1:tQumPT2UjD6uhggfAerRbPt+rWOPKC80DmgKUEqeGYo= 372 | github.com/operator-framework/operator-sdk v0.11.0/go.mod h1:Oo+O2br5qR6XSLWY/GgIvTvpsEKtzeWp+I3rHF0WIq8= 373 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= 374 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 375 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 376 | github.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= 377 | github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4= 378 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 379 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 380 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 381 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 382 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 383 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 384 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 385 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 386 | github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= 387 | github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 388 | github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 389 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 390 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 391 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= 392 | github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= 393 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 394 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 395 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 396 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= 397 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 398 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 399 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 400 | github.com/prometheus/common v0.0.0-20190104105734-b1c43a6df3ae/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 401 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 402 | github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= 403 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 404 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 405 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 406 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 407 | github.com/prometheus/procfs v0.0.0-20190104112138-b1a0a9a36d74/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 408 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 409 | github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= 410 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 411 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 412 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 413 | github.com/robfig/cron v0.0.0-20170526150127-736158dc09e1/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= 414 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 415 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 416 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 417 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 418 | github.com/rubenv/sql-migrate v0.0.0-20190618074426-f4d34eae5a5c/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= 419 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 420 | github.com/sclevine/spec v1.0.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= 421 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 422 | github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= 423 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 424 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 425 | github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 426 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 427 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 428 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 429 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 430 | github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 431 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 432 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 433 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 434 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 435 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 436 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 437 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 438 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 439 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 440 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 441 | github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= 442 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 443 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 444 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 445 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 446 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 447 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 448 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 449 | github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= 450 | github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A= 451 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 452 | github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 453 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 454 | github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= 455 | github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 456 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 457 | github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 458 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 459 | github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= 460 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 461 | github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= 462 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 463 | go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= 464 | go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= 465 | go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= 466 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 467 | go.opencensus.io v0.20.2 h1:NAfh7zF0/3/HqtMvJNZ/RFrSlCE6ZTlHmKfhL/Dm1Jk= 468 | go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 469 | go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 470 | go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= 471 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 472 | go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 473 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 474 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 475 | go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 476 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 477 | go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 478 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 479 | go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= 480 | golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= 481 | golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 482 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 483 | golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 484 | golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 485 | golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 486 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 487 | golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 488 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 489 | golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 490 | golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= 491 | golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 492 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= 493 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 494 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 495 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 496 | golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 497 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 498 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 499 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 500 | golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 501 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 502 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 503 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 504 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 505 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 506 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 507 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 508 | golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 509 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 510 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 511 | golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 512 | golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 513 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 514 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 515 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 516 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 517 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 518 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 519 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 520 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 521 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 522 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 523 | golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68= 524 | golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 525 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 526 | golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 527 | golang.org/x/oauth2 v0.0.0-20181105165119-ca4130e427c7/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 528 | golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 529 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 530 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 531 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= 532 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 533 | golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 534 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 535 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 536 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 537 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 538 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 539 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 540 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 541 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 542 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 543 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 544 | golang.org/x/sys v0.0.0-20181023152157-44b849a8bc13/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 545 | golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 546 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 547 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 548 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 549 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 550 | golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 551 | golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 552 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 553 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 554 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 555 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 556 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 557 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 558 | golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 559 | golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= 560 | golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 561 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 562 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 563 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 564 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 565 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 566 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 567 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 568 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 569 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 570 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 571 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 572 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 573 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 574 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 575 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 576 | golang.org/x/tools v0.0.0-20181011152555-a398e557df60/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 577 | golang.org/x/tools v0.0.0-20181207222222-4c874b978acb/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 578 | golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 579 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 580 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 581 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 582 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 583 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 584 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 585 | golang.org/x/tools v0.0.0-20190501045030-23463209683d/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 586 | golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 587 | golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 588 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= 589 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 590 | gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= 591 | gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= 592 | gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= 593 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 594 | gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= 595 | google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 596 | google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 597 | google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 598 | google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= 599 | google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= 600 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 601 | google.golang.org/api v0.3.2 h1:iTp+3yyl/KOtxa/d1/JUE0GGSoR6FuW5udver22iwpw= 602 | google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 603 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 604 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 605 | google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 606 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 607 | google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= 608 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 609 | google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 610 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 611 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 612 | google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 613 | google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 614 | google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= 615 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 616 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo= 617 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 618 | google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 619 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 620 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 621 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 622 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 623 | google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM= 624 | google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 625 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 626 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 627 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 628 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 629 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 630 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 631 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 632 | gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= 633 | gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 634 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 635 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 636 | gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 637 | gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 638 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 639 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 640 | gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 641 | gopkg.in/square/go-jose.v2 v2.3.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 642 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 643 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 644 | gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= 645 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 646 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 647 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 648 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 649 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 650 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 651 | grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= 652 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 653 | honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 654 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 655 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 656 | k8s.io/api v0.15.7 h1:Qoun2090uLk9jvaXX6I5b02mh8bKrluPcbvsGrOfAKE= 657 | k8s.io/api v0.15.7/go.mod h1:a/tUxscL+UxvYyA7Tj5DRc8ivYqJIO1Y5KDdlI6wSvo= 658 | k8s.io/apiextensions-apiserver v0.15.7 h1:7HeYMxRPsw3fAplLC54NTbszBLlGw3j9rZE5caAP5JY= 659 | k8s.io/apiextensions-apiserver v0.15.7/go.mod h1:ctb/NYtsiBt6CGN42Z+JrOkxi9nJYaKZYmatJ6SUy0Y= 660 | k8s.io/apimachinery v0.15.7 h1:H6pN003RwDju/3BzRGuymY4ymvMjHNbD8MVWhtDeaOI= 661 | k8s.io/apimachinery v0.15.7/go.mod h1:Xc10RHc1U+F/e9GCloJ8QAeCGevSVP5xhOhqlE+e1kM= 662 | k8s.io/apiserver v0.15.7/go.mod h1:d5Dbyt588GbBtUnbx9fSK+pYeqgZa32op+I1BmXiNuE= 663 | k8s.io/autoscaler v0.0.0-20190607113959-1b4f1855cb8e/go.mod h1:QEXezc9uKPT91dwqhSJq3GNI3B1HxFRQHiku9kmrsSA= 664 | k8s.io/cli-runtime v0.15.7/go.mod h1:ude0mtyxiQ4aRkjJaa3549lKnyhcYfQWa1IssUDVZZc= 665 | k8s.io/client-go v0.15.7 h1:ROclobh8vRf6kaKuhRKVXNWnBG+RthyKmWoM4kwWSI8= 666 | k8s.io/client-go v0.15.7/go.mod h1:QMNB76d3lKPvPQdOOnnxUF693C3hnCzUbC2umg70pWA= 667 | k8s.io/cloud-provider v0.15.7/go.mod h1:3jbv9TJlh2bawwLNHIr4J9W2tp0+CRZBj+NlGtbSzSM= 668 | k8s.io/code-generator v0.15.7/go.mod h1:G8bQwmHm2eafm5bgtX67XDZQ8CWKSGu9DekI+yN4Y5I= 669 | k8s.io/component-base v0.15.7/go.mod h1:iunfIII6uq3NC3S/EhBpKv8+eQ76vwlOYdFpyIeBk7g= 670 | k8s.io/gengo v0.0.0-20181106084056-51747d6e00da/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 671 | k8s.io/gengo v0.0.0-20181113154421-fd15ee9cc2f7/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 672 | k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 673 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 674 | k8s.io/gengo v0.0.0-20190327210449-e17681d19d3a/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 675 | k8s.io/helm v2.14.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= 676 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 677 | k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 678 | k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 679 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 680 | k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 681 | k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c= 682 | k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 683 | k8s.io/kube-aggregator v0.15.7/go.mod h1:lEwQHfhjFPTHKSGuq18zySmAadlTGvuSu1SajRA3nH4= 684 | k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 685 | k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 686 | k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 687 | k8s.io/kube-openapi v0.0.0-20190320154901-5e45bb682580/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 688 | k8s.io/kube-openapi v0.0.0-20190401085232-94e1e7b7574c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 689 | k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d h1:Xpe6sK+RY4ZgCTyZ3y273UmFmURhjtoJiwOMbQsXitY= 690 | k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= 691 | k8s.io/kube-openapi v0.0.0-20200204173128-addea2498afe h1:GOfbcWvX5wW2vcfNch83xYp9SDZjRgAJk+t373yaHKk= 692 | k8s.io/kube-state-metrics v1.7.2 h1:6vdtgXrrRRMSgnyDmgua+qvgCYv954JNfxXAtDkeLVQ= 693 | k8s.io/kube-state-metrics v1.7.2/go.mod h1:U2Y6DRi07sS85rmVPmBFlmv+2peBcL8IWGjM+IjYA/E= 694 | k8s.io/kubernetes v1.11.7-beta.0.0.20181219023948-b875d52ea96d/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= 695 | k8s.io/kubernetes v1.11.8-beta.0.0.20190124204751-3a10094374f2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= 696 | k8s.io/kubernetes v1.14.2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= 697 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= 698 | k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= 699 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y= 700 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 701 | modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= 702 | modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= 703 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 704 | modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 705 | modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= 706 | sigs.k8s.io/controller-runtime v0.1.10/go.mod h1:HFAYoOh6XMV+jKF1UjFwrknPbowfyHEHHRdJMf2jMX8= 707 | sigs.k8s.io/controller-runtime v0.2.0/go.mod h1:ZHqrRDZi3f6BzONcvlUxkqCKgwasGk5FZrnSv9TVZF4= 708 | sigs.k8s.io/controller-runtime v0.3.0 h1:ZtdgqJXVHsIytjdmDuk0QjagnzyLq9FjojXRqIp+dU4= 709 | sigs.k8s.io/controller-runtime v0.3.0/go.mod h1:Cw6PkEg0Sa7dAYovGT4R0tRkGhHXpYijwNxYhAnAZZk= 710 | sigs.k8s.io/controller-tools v0.2.0/go.mod h1:8t/X+FVWvk6TaBcsa+UKUBbn7GMtvyBKX30SGl4em6Y= 711 | sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= 712 | sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 713 | sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 714 | sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs= 715 | sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= 716 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 717 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 718 | vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= 719 | --------------------------------------------------------------------------------