├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── foo_types.go │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── bin └── manager ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ └── samplecontroller.k8s.io_foos.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_foos.yaml │ │ └── webhook_in_foos.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_prometheus_metrics_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── rbac │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ └── role_binding.yaml ├── samples │ └── samplecontroller_v1alpha1_foo.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── controllers ├── foo_controller.go ├── foo_controller_test.go └── suite_test.go ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.12.5 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY main.go main.go 14 | COPY api/ api/ 15 | COPY controllers/ controllers/ 16 | 17 | # Build 18 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go 19 | 20 | # Use distroless as minimal base image to package the manager binary 21 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 22 | FROM gcr.io/distroless/static:latest 23 | WORKDIR / 24 | COPY --from=builder /workspace/manager . 25 | ENTRYPOINT ["/manager"] 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 go_vargo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Image URL to use all building/pushing image targets 3 | IMG ?= controller:latest 4 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 5 | CRD_OPTIONS ?= "crd:trivialVersions=true" 6 | 7 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 8 | ifeq (,$(shell go env GOBIN)) 9 | GOBIN=$(shell go env GOPATH)/bin 10 | else 11 | GOBIN=$(shell go env GOBIN) 12 | endif 13 | 14 | all: manager 15 | 16 | # Run tests 17 | test: generate fmt vet manifests 18 | go test ./... -coverprofile cover.out 19 | 20 | # Build manager binary 21 | manager: generate fmt vet 22 | go build -o bin/manager main.go 23 | 24 | # Run against the configured Kubernetes cluster in ~/.kube/config 25 | run: generate fmt vet manifests 26 | go run ./main.go 27 | 28 | # Install CRDs into a cluster 29 | install: manifests 30 | kustomize build config/crd | kubectl apply -f - 31 | 32 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 33 | deploy: manifests 34 | cd config/manager && kustomize edit set image controller=${IMG} 35 | kustomize build config/default | kubectl apply -f - 36 | 37 | # Generate manifests e.g. CRD, RBAC etc. 38 | manifests: controller-gen 39 | $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases 40 | 41 | # Run go fmt against code 42 | fmt: 43 | go fmt ./... 44 | 45 | # Run go vet against code 46 | vet: 47 | go vet ./... 48 | 49 | # Generate code 50 | generate: controller-gen 51 | $(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths="./..." 52 | 53 | # Build the docker image 54 | docker-build: test 55 | docker build . -t ${IMG} 56 | 57 | # Push the docker image 58 | docker-push: 59 | docker push ${IMG} 60 | 61 | # find or download controller-gen 62 | # download controller-gen if necessary 63 | controller-gen: 64 | ifeq (, $(shell which controller-gen)) 65 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.0 66 | CONTROLLER_GEN=$(GOBIN)/controller-gen 67 | else 68 | CONTROLLER_GEN=$(shell which controller-gen) 69 | endif 70 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | version: "2" 2 | domain: k8s.io 3 | repo: github.com/govargo/sample-controller-kubebuilder 4 | resources: 5 | - group: samplecontroller 6 | version: v1alpha1 7 | kind: Foo 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sample Controller(Foo Controller) 2 | 3 | This Controller is developed by Kubebuilder v2.0.0 4 | 5 | 技術書典7で頒布した「実践入門 Kubernetesカスタムコントローラへの道」の第五章に掲載したサンプルコード用のリポジトリです。 6 | 7 | ## What is this Controller 8 | 9 | This Controller reconcile foo custom resource. 10 | 11 | Foo resource owns deployment object. 12 | 13 | We define foo.spec.deploymentName & foo.spec.replicas. 14 | When we apply foo, then deployment owned by foo is created. 15 | 16 | If we delete deployment object like 'kubectl delete deployment/\', 17 | foo reconcile it and the deployment is created again. 18 | 19 | Or if we scale deployment object like 'kubectl scale deployment/\ --replicas 0', 20 | foo reconcile it and the deployment keeps the foo.spec.replicas. 21 | 22 | ## How to run 23 | 24 | Clone source code 25 | 26 | ``` 27 | $ mkdir -p $GOPATH/src/github.com/govargo 28 | $ cd $GOPATH/src/github.com/govargo 29 | $ git clone https://github.com/govargo/sample-controller-kubebuilder.git 30 | $ cd sample-controller-kubebuilder 31 | ``` 32 | 33 | Run localy 34 | 35 | ``` 36 | $ make install 37 | $ make run 38 | $ kubectl apply -f config/samples/samplecontroller_v1alpha1_foo.yaml 39 | ``` 40 | 41 | Run container as Deployment 42 | 43 | ``` 44 | $ make install 45 | $ make deploy 46 | $ kubectl apply -f config/samples/samplecontroller_v1alpha1_foo.yaml 47 | ``` 48 | 49 | ## Reference 50 | 51 | This project is inspired by 52 | 53 | * https://github.com/kubernetes/sample-controller 54 | * https://github.com/jetstack/kubebuilder-sample-controller 55 | -------------------------------------------------------------------------------- /api/v1alpha1/foo_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 | ) 21 | 22 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 23 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 24 | 25 | // FooSpec defines the desired state of Foo 26 | type FooSpec struct { 27 | // +kubebuilder:validation:Required 28 | // +kubebuilder:validation:Format:=string 29 | 30 | // the name of deployment which is owned by foo 31 | DeploymentName string `json:"deploymentName"` 32 | 33 | // +kubebuilder:validation:Required 34 | // +kubebuilder:validation:Minimum=0 35 | 36 | // the replicas of deployment which is owned by foo 37 | Replicas *int32 `json:"replicas"` 38 | } 39 | 40 | // FooStatus defines the observed state of Foo 41 | type FooStatus struct { 42 | // this is equal deployment.status.availableReplicas 43 | // +optional 44 | AvailableReplicas int32 `json:"availableReplicas"` 45 | } 46 | 47 | // +kubebuilder:object:root=true 48 | // +kubebuilder:subresource:status 49 | 50 | // Foo is the Schema for the foos API 51 | type Foo struct { 52 | metav1.TypeMeta `json:",inline"` 53 | metav1.ObjectMeta `json:"metadata,omitempty"` 54 | 55 | Spec FooSpec `json:"spec,omitempty"` 56 | Status FooStatus `json:"status,omitempty"` 57 | } 58 | 59 | // +kubebuilder:object:root=true 60 | 61 | // FooList contains a list of Foo 62 | type FooList struct { 63 | metav1.TypeMeta `json:",inline"` 64 | metav1.ListMeta `json:"metadata,omitempty"` 65 | Items []Foo `json:"items"` 66 | } 67 | 68 | func init() { 69 | SchemeBuilder.Register(&Foo{}, &FooList{}) 70 | } 71 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | // Package v1alpha1 contains API Schema definitions for the samplecontroller v1alpha1 API group 17 | // +kubebuilder:object:generate=true 18 | // +groupName=samplecontroller.k8s.io 19 | package v1alpha1 20 | 21 | import ( 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | var ( 27 | // GroupVersion is group version used to register these objects 28 | GroupVersion = schema.GroupVersion{Group: "samplecontroller.k8s.io", Version: "v1alpha1"} 29 | 30 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 31 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 32 | 33 | // AddToScheme adds the types in this group-version to the given scheme. 34 | AddToScheme = SchemeBuilder.AddToScheme 35 | ) 36 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | // Code generated by controller-gen. DO NOT EDIT. 19 | 20 | package v1alpha1 21 | 22 | import ( 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | ) 25 | 26 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 27 | func (in *Foo) DeepCopyInto(out *Foo) { 28 | *out = *in 29 | out.TypeMeta = in.TypeMeta 30 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 31 | in.Spec.DeepCopyInto(&out.Spec) 32 | out.Status = in.Status 33 | } 34 | 35 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Foo. 36 | func (in *Foo) DeepCopy() *Foo { 37 | if in == nil { 38 | return nil 39 | } 40 | out := new(Foo) 41 | in.DeepCopyInto(out) 42 | return out 43 | } 44 | 45 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 46 | func (in *Foo) DeepCopyObject() runtime.Object { 47 | if c := in.DeepCopy(); c != nil { 48 | return c 49 | } 50 | return nil 51 | } 52 | 53 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 54 | func (in *FooList) DeepCopyInto(out *FooList) { 55 | *out = *in 56 | out.TypeMeta = in.TypeMeta 57 | out.ListMeta = in.ListMeta 58 | if in.Items != nil { 59 | in, out := &in.Items, &out.Items 60 | *out = make([]Foo, len(*in)) 61 | for i := range *in { 62 | (*in)[i].DeepCopyInto(&(*out)[i]) 63 | } 64 | } 65 | } 66 | 67 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FooList. 68 | func (in *FooList) DeepCopy() *FooList { 69 | if in == nil { 70 | return nil 71 | } 72 | out := new(FooList) 73 | in.DeepCopyInto(out) 74 | return out 75 | } 76 | 77 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 78 | func (in *FooList) DeepCopyObject() runtime.Object { 79 | if c := in.DeepCopy(); c != nil { 80 | return c 81 | } 82 | return nil 83 | } 84 | 85 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 86 | func (in *FooSpec) DeepCopyInto(out *FooSpec) { 87 | *out = *in 88 | if in.Replicas != nil { 89 | in, out := &in.Replicas, &out.Replicas 90 | *out = new(int32) 91 | **out = **in 92 | } 93 | } 94 | 95 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FooSpec. 96 | func (in *FooSpec) DeepCopy() *FooSpec { 97 | if in == nil { 98 | return nil 99 | } 100 | out := new(FooSpec) 101 | in.DeepCopyInto(out) 102 | return out 103 | } 104 | 105 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 106 | func (in *FooStatus) DeepCopyInto(out *FooStatus) { 107 | *out = *in 108 | } 109 | 110 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FooStatus. 111 | func (in *FooStatus) DeepCopy() *FooStatus { 112 | if in == nil { 113 | return nil 114 | } 115 | out := new(FooStatus) 116 | in.DeepCopyInto(out) 117 | return out 118 | } 119 | -------------------------------------------------------------------------------- /bin/manager: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/govargo/sample-controller-kubebuilder/75256305e9ab71e5838cd80285d3f7f30f399526/bin/manager -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | apiVersion: certmanager.k8s.io/v1alpha1 4 | kind: Issuer 5 | metadata: 6 | name: selfsigned-issuer 7 | namespace: system 8 | spec: 9 | selfSigned: {} 10 | --- 11 | apiVersion: certmanager.k8s.io/v1alpha1 12 | kind: Certificate 13 | metadata: 14 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 15 | namespace: system 16 | spec: 17 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 18 | commonName: $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 19 | dnsNames: 20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 21 | issuerRef: 22 | kind: Issuer 23 | name: selfsigned-issuer 24 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 25 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: certmanager.k8s.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: certmanager.k8s.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: certmanager.k8s.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: certmanager.k8s.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/bases/samplecontroller.k8s.io_foos.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | creationTimestamp: null 7 | name: foos.samplecontroller.k8s.io 8 | spec: 9 | group: samplecontroller.k8s.io 10 | names: 11 | kind: Foo 12 | plural: foos 13 | scope: "" 14 | subresources: 15 | status: {} 16 | validation: 17 | openAPIV3Schema: 18 | description: Foo is the Schema for the foos API 19 | properties: 20 | apiVersion: 21 | description: 'APIVersion defines the versioned schema of this representation 22 | of an object. Servers should convert recognized schemas to the latest 23 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' 24 | type: string 25 | kind: 26 | description: 'Kind is a string value representing the REST resource this 27 | object represents. Servers may infer this from the endpoint the client 28 | 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 30 | metadata: 31 | type: object 32 | spec: 33 | description: FooSpec defines the desired state of Foo 34 | properties: 35 | deploymentName: 36 | description: the name of deployment which is owned by foo 37 | format: string 38 | type: string 39 | replicas: 40 | description: the replicas of deployment which is owned by foo 41 | format: int32 42 | minimum: 0 43 | type: integer 44 | required: 45 | - deploymentName 46 | - replicas 47 | type: object 48 | status: 49 | description: FooStatus defines the observed state of Foo 50 | properties: 51 | availableReplicas: 52 | description: this is equal deployment.status.availableReplicas 53 | format: int32 54 | type: integer 55 | type: object 56 | type: object 57 | version: v1alpha1 58 | versions: 59 | - name: v1alpha1 60 | served: true 61 | storage: true 62 | status: 63 | acceptedNames: 64 | kind: "" 65 | plural: "" 66 | conditions: [] 67 | storedVersions: [] 68 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/samplecontroller.k8s.io_foos.yaml 6 | # +kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_foos.yaml 12 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- patches/cainjection_in_foos.yaml 17 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_foos.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: foos.samplecontroller.k8s.io 9 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_foos.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: foos.samplecontroller.k8s.io 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: sample-controller-kubebuilder-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: sample-controller-kubebuilder- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml 20 | #- ../webhook 21 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 22 | #- ../certmanager 23 | 24 | patchesStrategicMerge: 25 | # Protect the /metrics endpoint by putting it behind auth. 26 | # Only one of manager_auth_proxy_patch.yaml and 27 | # manager_prometheus_metrics_patch.yaml should be enabled. 28 | - manager_auth_proxy_patch.yaml 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, uncomment the following line and 31 | # comment manager_auth_proxy_patch.yaml. 32 | # Only one of manager_auth_proxy_patch.yaml and 33 | # manager_prometheus_metrics_patch.yaml should be enabled. 34 | #- manager_prometheus_metrics_patch.yaml 35 | 36 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml 37 | #- manager_webhook_patch.yaml 38 | 39 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 40 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 41 | # 'CERTMANAGER' needs to be enabled to use ca injection 42 | #- webhookcainjection_patch.yaml 43 | 44 | # the following config is for teaching kustomize how to do var substitution 45 | vars: 46 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 47 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 48 | # objref: 49 | # kind: Certificate 50 | # group: certmanager.k8s.io 51 | # version: v1alpha1 52 | # name: serving-cert # this name should match the one in certificate.yaml 53 | # fieldref: 54 | # fieldpath: metadata.namespace 55 | #- name: CERTIFICATE_NAME 56 | # objref: 57 | # kind: Certificate 58 | # group: certmanager.k8s.io 59 | # version: v1alpha1 60 | # name: serving-cert # this name should match the one in certificate.yaml 61 | #- name: SERVICE_NAMESPACE # namespace of the service 62 | # objref: 63 | # kind: Service 64 | # version: v1 65 | # name: webhook-service 66 | # fieldref: 67 | # fieldpath: metadata.namespace 68 | #- name: SERVICE_NAME 69 | # objref: 70 | # kind: Service 71 | # version: v1 72 | # name: webhook-service 73 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the controller manager, 2 | # it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--metrics-addr=127.0.0.1:8080" 25 | - "--enable-leader-election" 26 | -------------------------------------------------------------------------------- /config/default/manager_prometheus_metrics_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch enables Prometheus scraping for the manager pod. 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | metadata: 10 | annotations: 11 | prometheus.io/scrape: 'true' 12 | spec: 13 | containers: 14 | # Expose the prometheus metrics on default port 15 | - name: manager 16 | ports: 17 | - containerPort: 8080 18 | name: metrics 19 | protocol: TCP 20 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1beta1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: govargo/foo-controller 8 | newTag: kubebuilder 9 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | containers: 26 | - command: 27 | - /manager 28 | args: 29 | - --enable-leader-election 30 | image: controller:latest 31 | name: manager 32 | resources: 33 | limits: 34 | cpu: 100m 35 | memory: 30Mi 36 | requests: 37 | cpu: 100m 38 | memory: 20Mi 39 | terminationGracePeriodSeconds: 10 40 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | prometheus.io/port: "8443" 6 | prometheus.io/scheme: https 7 | prometheus.io/scrape: "true" 8 | labels: 9 | control-plane: controller-manager 10 | name: metrics-service 11 | namespace: system 12 | spec: 13 | ports: 14 | - name: https 15 | port: 8443 16 | targetPort: https 17 | selector: 18 | control-plane: controller-manager 19 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 3 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - "" 11 | resources: 12 | - events 13 | verbs: 14 | - create 15 | - patch 16 | - apiGroups: 17 | - apps 18 | resources: 19 | - deployments 20 | verbs: 21 | - create 22 | - delete 23 | - get 24 | - list 25 | - update 26 | - watch 27 | - apiGroups: 28 | - samplecontroller.k8s.io 29 | resources: 30 | - foos 31 | verbs: 32 | - create 33 | - delete 34 | - get 35 | - list 36 | - patch 37 | - update 38 | - watch 39 | - apiGroups: 40 | - samplecontroller.k8s.io 41 | resources: 42 | - foos/status 43 | verbs: 44 | - get 45 | - patch 46 | - update 47 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/samples/samplecontroller_v1alpha1_foo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: samplecontroller.k8s.io/v1alpha1 2 | kind: Foo 3 | metadata: 4 | name: foo-sample 5 | spec: 6 | deploymentName: example-foo 7 | replicas: 1 8 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/govargo/sample-controller-kubebuilder/75256305e9ab71e5838cd80285d3f7f30f399526/config/webhook/manifests.yaml -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 443 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /controllers/foo_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package controllers 17 | 18 | import ( 19 | "context" 20 | 21 | "github.com/go-logr/logr" 22 | appsv1 "k8s.io/api/apps/v1" 23 | corev1 "k8s.io/api/core/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/client-go/tools/record" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | 30 | samplecontrollerv1alpha1 "github.com/govargo/sample-controller-kubebuilder/api/v1alpha1" 31 | ) 32 | 33 | // FooReconciler reconciles a Foo object 34 | type FooReconciler struct { 35 | client.Client 36 | Log logr.Logger 37 | Scheme *runtime.Scheme 38 | Recorder record.EventRecorder 39 | } 40 | 41 | // +kubebuilder:rbac:groups=samplecontroller.k8s.io,resources=foos,verbs=get;list;watch;create;update;patch;delete 42 | // +kubebuilder:rbac:groups=samplecontroller.k8s.io,resources=foos/status,verbs=get;update;patch 43 | // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;delete 44 | // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch 45 | 46 | func (r *FooReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 47 | ctx := context.Background() 48 | log := r.Log.WithValues("foo", req.NamespacedName) 49 | 50 | /* 51 | ### 1: Load the Foo by name 52 | We'll fetch the Foo using our client. 53 | All client methods take a context (to allow for cancellation) as 54 | their first argument, and the object 55 | in question as their last. 56 | Get is a bit special, in that it takes a 57 | [`NamespacedName`](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/client#ObjectKey) 58 | as the middle argument (most don't have a middle argument, as we'll see below). 59 | Many client methods also take variadic options at the end. 60 | */ 61 | var foo samplecontrollerv1alpha1.Foo 62 | log.Info("fetching Foo Resource") 63 | if err := r.Get(ctx, req.NamespacedName, &foo); err != nil { 64 | log.Error(err, "unable to fetch Foo") 65 | // we'll ignore not-found errors, since they can't be fixed by an immediate 66 | // requeue (we'll need to wait for a new notification), and we can get them 67 | // on deleted requests. 68 | return ctrl.Result{}, client.IgnoreNotFound(err) 69 | } 70 | 71 | /* 72 | ### 2: Clean Up old Deployment which had been owned by Foo Resource. 73 | We'll find deployment object which foo object owns. 74 | If there is a deployment which is owned by foo and it doesn't match foo.spec.deploymentName, 75 | we clean up the deployment object. 76 | (If we do nothing without this func, the old deployment object keeps existing.) 77 | */ 78 | if err := r.cleanupOwnedResources(ctx, log, &foo); err != nil { 79 | log.Error(err, "failed to clean up old Deployment resources for this Foo") 80 | return ctrl.Result{}, err 81 | } 82 | 83 | /* 84 | ### 3: Create or Update deployment object which match foo.Spec. 85 | We'll use ctrl.CreateOrUpdate method. 86 | It enable us to create an object if it doesn't exist. 87 | Or it enable us to update the object if it exists. 88 | */ 89 | 90 | // get deploymentName from foo.Spec 91 | deploymentName := foo.Spec.DeploymentName 92 | 93 | // define deployment template using deploymentName 94 | deploy := &appsv1.Deployment{ 95 | ObjectMeta: metav1.ObjectMeta{ 96 | Name: deploymentName, 97 | Namespace: req.Namespace, 98 | }, 99 | } 100 | 101 | // Create or Update deployment object 102 | if _, err := ctrl.CreateOrUpdate(ctx, r.Client, deploy, func() error { 103 | 104 | // set the replicas from foo.Spec 105 | replicas := int32(1) 106 | if foo.Spec.Replicas != nil { 107 | replicas = *foo.Spec.Replicas 108 | } 109 | deploy.Spec.Replicas = &replicas 110 | 111 | // set a label for our deployment 112 | labels := map[string]string{ 113 | "app": "nginx", 114 | "controller": req.Name, 115 | } 116 | 117 | // set labels to spec.selector for our deployment 118 | if deploy.Spec.Selector == nil { 119 | deploy.Spec.Selector = &metav1.LabelSelector{MatchLabels: labels} 120 | } 121 | 122 | // set labels to template.objectMeta for our deployment 123 | if deploy.Spec.Template.ObjectMeta.Labels == nil { 124 | deploy.Spec.Template.ObjectMeta.Labels = labels 125 | } 126 | 127 | // set a container for our deployment 128 | containers := []corev1.Container{ 129 | { 130 | Name: "nginx", 131 | Image: "nginx:latest", 132 | }, 133 | } 134 | 135 | // set containers to template.spec.containers for our deployment 136 | if deploy.Spec.Template.Spec.Containers == nil { 137 | deploy.Spec.Template.Spec.Containers = containers 138 | } 139 | 140 | // set the owner so that garbage collection can kicks in 141 | if err := ctrl.SetControllerReference(&foo, deploy, r.Scheme); err != nil { 142 | log.Error(err, "unable to set ownerReference from Foo to Deployment") 143 | return err 144 | } 145 | 146 | // end of ctrl.CreateOrUpdate 147 | return nil 148 | 149 | }); err != nil { 150 | 151 | // error handling of ctrl.CreateOrUpdate 152 | log.Error(err, "unable to ensure deployment is correct") 153 | return ctrl.Result{}, err 154 | 155 | } 156 | 157 | /* 158 | ### 4: Update foo status. 159 | First, we get deployment object from in-memory-cache. 160 | Second, we get deployment.status.AvailableReplicas in order to update foo.status.AvailableReplicas. 161 | Third, we update foo.status from deployment.status.AvailableReplicas. 162 | Finally, finish reconcile. and the next reconcile loop would start unless controller process ends. 163 | */ 164 | 165 | // get deployment object from in-memory-cache 166 | var deployment appsv1.Deployment 167 | var deploymentNamespacedName = client.ObjectKey{Namespace: req.Namespace, Name: foo.Spec.DeploymentName} 168 | if err := r.Get(ctx, deploymentNamespacedName, &deployment); err != nil { 169 | log.Error(err, "unable to fetch Deployment") 170 | // we'll ignore not-found errors, since they can't be fixed by an immediate 171 | // requeue (we'll need to wait for a new notification), and we can get them 172 | // on deleted requests. 173 | return ctrl.Result{}, client.IgnoreNotFound(err) 174 | } 175 | 176 | // set foo.status.AvailableReplicas from deployment 177 | availableReplicas := deployment.Status.AvailableReplicas 178 | if availableReplicas == foo.Status.AvailableReplicas { 179 | // if availableReplicas equals availableReplicas, we wouldn't update anything. 180 | // exit Reconcile func without updating foo.status 181 | return ctrl.Result{}, nil 182 | } 183 | foo.Status.AvailableReplicas = availableReplicas 184 | 185 | // update foo.status 186 | if err := r.Status().Update(ctx, &foo); err != nil { 187 | log.Error(err, "unable to update Foo status") 188 | return ctrl.Result{}, err 189 | } 190 | 191 | // create event for updated foo.status 192 | r.Recorder.Eventf(&foo, corev1.EventTypeNormal, "Updated", "Update foo.status.AvailableReplicas: %d", foo.Status.AvailableReplicas) 193 | 194 | return ctrl.Result{}, nil 195 | } 196 | 197 | // cleanupOwnedResources will delete any existing Deployment resources that 198 | // were created for the given Foo that no longer match the 199 | // foo.spec.deploymentName field. 200 | func (r *FooReconciler) cleanupOwnedResources(ctx context.Context, log logr.Logger, foo *samplecontrollerv1alpha1.Foo) error { 201 | log.Info("finding existing Deployments for Foo resource") 202 | 203 | // List all deployment resources owned by this Foo 204 | var deployments appsv1.DeploymentList 205 | if err := r.List(ctx, &deployments, client.InNamespace(foo.Namespace), client.MatchingFields(map[string]string{deploymentOwnerKey: foo.Name})); err != nil { 206 | return err 207 | } 208 | 209 | // Delete deployment if the deployment name doesn't match foo.spec.deploymentName 210 | for _, deployment := range deployments.Items { 211 | if deployment.Name == foo.Spec.DeploymentName { 212 | // If this deployment's name matches the one on the Foo resource 213 | // then do not delete it. 214 | continue 215 | } 216 | 217 | // Delete old deployment object which doesn't match foo.spec.deploymentName 218 | if err := r.Delete(ctx, &deployment); err != nil { 219 | log.Error(err, "failed to delete Deployment resource") 220 | return err 221 | } 222 | 223 | log.Info("delete deployment resource: " + deployment.Name) 224 | r.Recorder.Eventf(foo, corev1.EventTypeNormal, "Deleted", "Deleted deployment %q", deployment.Name) 225 | } 226 | 227 | return nil 228 | } 229 | 230 | var ( 231 | deploymentOwnerKey = ".metadata.controller" 232 | apiGVStr = samplecontrollerv1alpha1.GroupVersion.String() 233 | ) 234 | 235 | // setup with controller manager 236 | func (r *FooReconciler) SetupWithManager(mgr ctrl.Manager) error { 237 | 238 | // add deploymentOwnerKey index to deployment object which foo resource owns 239 | if err := mgr.GetFieldIndexer().IndexField(&appsv1.Deployment{}, deploymentOwnerKey, func(rawObj runtime.Object) []string { 240 | // grab the deployment object, extract the owner... 241 | deployment := rawObj.(*appsv1.Deployment) 242 | owner := metav1.GetControllerOf(deployment) 243 | if owner == nil { 244 | return nil 245 | } 246 | // ...make sure it's a Foo... 247 | if owner.APIVersion != apiGVStr || owner.Kind != "Foo" { 248 | return nil 249 | } 250 | 251 | // ...and if so, return it 252 | return []string{owner.Name} 253 | }); err != nil { 254 | return err 255 | } 256 | 257 | // define to watch targets...Foo resource and owned Deployment 258 | return ctrl.NewControllerManagedBy(mgr). 259 | For(&samplecontrollerv1alpha1.Foo{}). 260 | Owns(&appsv1.Deployment{}). 261 | Complete(r) 262 | } 263 | -------------------------------------------------------------------------------- /controllers/foo_controller_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | appsv1 "k8s.io/api/apps/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "k8s.io/utils/pointer" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | 15 | samplecontrollerv1alpha1 "github.com/govargo/sample-controller-kubebuilder/api/v1alpha1" 16 | ) 17 | 18 | var _ = Context("Inside of a new namespace", func() { 19 | ctx := context.TODO() 20 | ns := SetupTest(ctx) 21 | 22 | Describe("when no existing resources exist", func() { 23 | 24 | It("should create a new Deployment resource with the specified name and one replica if one replica is provided", func() { 25 | foo := &samplecontrollerv1alpha1.Foo{ 26 | ObjectMeta: metav1.ObjectMeta{ 27 | Name: "example-foo", 28 | Namespace: ns.Name, 29 | }, 30 | Spec: samplecontrollerv1alpha1.FooSpec{ 31 | DeploymentName: "example-foo", 32 | Replicas: pointer.Int32Ptr(1), 33 | }, 34 | } 35 | 36 | err := k8sClient.Create(ctx, foo) 37 | Expect(err).NotTo(HaveOccurred(), "failed to create test Foo resource") 38 | 39 | deployment := &appsv1.Deployment{} 40 | Eventually( 41 | getResourceFunc(ctx, client.ObjectKey{Name: "example-foo", Namespace: foo.Namespace}, deployment), 42 | time.Second*5, time.Millisecond*500).Should(BeNil()) 43 | 44 | Expect(*deployment.Spec.Replicas).To(Equal(int32(1))) 45 | }) 46 | 47 | It("should create a new Deployment resource with the specified name and two replicas if two is specified", func() { 48 | foo := &samplecontrollerv1alpha1.Foo{ 49 | ObjectMeta: metav1.ObjectMeta{ 50 | Name: "example-foo", 51 | Namespace: ns.Name, 52 | }, 53 | Spec: samplecontrollerv1alpha1.FooSpec{ 54 | DeploymentName: "example-foo", 55 | Replicas: pointer.Int32Ptr(2), 56 | }, 57 | } 58 | 59 | err := k8sClient.Create(ctx, foo) 60 | Expect(err).NotTo(HaveOccurred(), "failed to create test Foo resource") 61 | 62 | deployment := &appsv1.Deployment{} 63 | Eventually( 64 | getResourceFunc(ctx, client.ObjectKey{Name: "example-foo", Namespace: foo.Namespace}, deployment), 65 | time.Second*5, time.Millisecond*500).Should(BeNil()) 66 | 67 | Expect(*deployment.Spec.Replicas).To(Equal(int32(2))) 68 | }) 69 | 70 | It("should allow updating the replicas count after creating a Foo resource", func() { 71 | deploymentObjectKey := client.ObjectKey{ 72 | Name: "example-foo", 73 | Namespace: ns.Name, 74 | } 75 | fooObjectKey := client.ObjectKey{ 76 | Name: "example-foo", 77 | Namespace: ns.Name, 78 | } 79 | foo := &samplecontrollerv1alpha1.Foo{ 80 | ObjectMeta: metav1.ObjectMeta{ 81 | Name: fooObjectKey.Name, 82 | Namespace: fooObjectKey.Namespace, 83 | }, 84 | Spec: samplecontrollerv1alpha1.FooSpec{ 85 | DeploymentName: deploymentObjectKey.Name, 86 | Replicas: pointer.Int32Ptr(1), 87 | }, 88 | } 89 | 90 | err := k8sClient.Create(ctx, foo) 91 | Expect(err).NotTo(HaveOccurred(), "failed to create test Foo resource") 92 | 93 | deployment := &appsv1.Deployment{} 94 | Eventually( 95 | getResourceFunc(ctx, deploymentObjectKey, deployment), 96 | time.Second*5, time.Millisecond*500).Should(BeNil(), "deployment resource should exist") 97 | 98 | Expect(*deployment.Spec.Replicas).To(Equal(int32(1)), "replica count should be equal to 1") 99 | 100 | err = k8sClient.Get(ctx, fooObjectKey, foo) 101 | Expect(err).NotTo(HaveOccurred(), "failed to retrieve Foo resource") 102 | 103 | foo.Spec.Replicas = pointer.Int32Ptr(2) 104 | err = k8sClient.Update(ctx, foo) 105 | Expect(err).NotTo(HaveOccurred(), "failed to Update Foo resource") 106 | 107 | Eventually(getDeploymentReplicasFunc(ctx, deploymentObjectKey)). 108 | Should(Equal(int32(2)), "expected Deployment resource to be scale to 2 replicas") 109 | }) 110 | 111 | It("should clean up an old Deployment resource if the deploymentName is changed", func() { 112 | deploymentObjectKey := client.ObjectKey{ 113 | Name: "example-foo", 114 | Namespace: ns.Name, 115 | } 116 | newDeploymentObjectKey := client.ObjectKey{ 117 | Name: "example-foo-new", 118 | Namespace: ns.Name, 119 | } 120 | fooObjectKey := client.ObjectKey{ 121 | Name: "example-foo", 122 | Namespace: ns.Name, 123 | } 124 | foo := &samplecontrollerv1alpha1.Foo{ 125 | ObjectMeta: metav1.ObjectMeta{ 126 | Name: fooObjectKey.Name, 127 | Namespace: fooObjectKey.Namespace, 128 | }, 129 | Spec: samplecontrollerv1alpha1.FooSpec{ 130 | DeploymentName: deploymentObjectKey.Name, 131 | Replicas: pointer.Int32Ptr(1), 132 | }, 133 | } 134 | 135 | err := k8sClient.Create(ctx, foo) 136 | Expect(err).NotTo(HaveOccurred(), "failed to create test Foo resource") 137 | 138 | deployment := &appsv1.Deployment{} 139 | Eventually( 140 | getResourceFunc(ctx, deploymentObjectKey, deployment), 141 | time.Second*5, time.Millisecond*500).Should(BeNil(), "deployment resource should exist") 142 | 143 | err = k8sClient.Get(ctx, fooObjectKey, foo) 144 | Expect(err).NotTo(HaveOccurred(), "failed to retrieve Foo resource") 145 | 146 | foo.Spec.DeploymentName = newDeploymentObjectKey.Name 147 | err = k8sClient.Update(ctx, foo) 148 | Expect(err).NotTo(HaveOccurred(), "failed to Update Foo resource") 149 | 150 | Eventually( 151 | getResourceFunc(ctx, deploymentObjectKey, deployment), 152 | time.Second*5, time.Millisecond*500).ShouldNot(BeNil(), "old deployment resource should be deleted") 153 | 154 | Eventually( 155 | getResourceFunc(ctx, newDeploymentObjectKey, deployment), 156 | time.Second*5, time.Millisecond*500).Should(BeNil(), "new deployment resource should be created") 157 | }) 158 | }) 159 | }) 160 | 161 | func getResourceFunc(ctx context.Context, key client.ObjectKey, obj runtime.Object) func() error { 162 | return func() error { 163 | return k8sClient.Get(ctx, key, obj) 164 | } 165 | } 166 | 167 | func getDeploymentReplicasFunc(ctx context.Context, key client.ObjectKey) func() int32 { 168 | return func() int32 { 169 | depl := &appsv1.Deployment{} 170 | err := k8sClient.Get(ctx, key, depl) 171 | Expect(err).NotTo(HaveOccurred(), "failed to get Deployment resource") 172 | 173 | return *depl.Spec.Replicas 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | Unless required by applicable law or agreed to in writing, software 7 | distributed under the License is distributed on an "AS IS" BASIS, 8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | See the License for the specific language governing permissions and 10 | limitations under the License. 11 | */ 12 | 13 | package controllers 14 | 15 | import ( 16 | "context" 17 | "math/rand" 18 | "path/filepath" 19 | "testing" 20 | "time" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | 25 | samplecontrollerv1alpha1 "github.com/govargo/sample-controller-kubebuilder/api/v1alpha1" 26 | corev1 "k8s.io/api/core/v1" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "k8s.io/client-go/kubernetes/scheme" 29 | "k8s.io/client-go/rest" 30 | ctrl "sigs.k8s.io/controller-runtime" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | "sigs.k8s.io/controller-runtime/pkg/envtest" 33 | logf "sigs.k8s.io/controller-runtime/pkg/log" 34 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 35 | // +kubebuilder:scaffold:imports 36 | ) 37 | 38 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 39 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 40 | 41 | var cfg *rest.Config 42 | var k8sClient client.Client 43 | var testEnv *envtest.Environment 44 | 45 | func TestAPIs(t *testing.T) { 46 | RegisterFailHandler(Fail) 47 | 48 | RunSpecsWithDefaultAndCustomReporters(t, 49 | "Controller Suite", 50 | []Reporter{envtest.NewlineReporter{}}) 51 | } 52 | 53 | var _ = BeforeSuite(func(done Done) { 54 | logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) 55 | 56 | By("bootstrapping test environment") 57 | testEnv = &envtest.Environment{ 58 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 59 | } 60 | 61 | var err error 62 | cfg, err = testEnv.Start() 63 | Expect(err).ToNot(HaveOccurred()) 64 | Expect(cfg).ToNot(BeNil()) 65 | 66 | err = samplecontrollerv1alpha1.AddToScheme(scheme.Scheme) 67 | Expect(err).NotTo(HaveOccurred()) 68 | 69 | // +kubebuilder:scaffold:scheme 70 | 71 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 72 | Expect(err).ToNot(HaveOccurred()) 73 | Expect(k8sClient).ToNot(BeNil()) 74 | 75 | close(done) 76 | }, 60) 77 | 78 | var _ = AfterSuite(func() { 79 | By("tearing down the test environment") 80 | err := testEnv.Stop() 81 | Expect(err).ToNot(HaveOccurred()) 82 | }) 83 | 84 | // SetupTest will set up a testing environment. 85 | // This includes: 86 | // * creating a Namespace to be used during the test 87 | // * starting the 'FooReconciler' 88 | // * stopping the 'FooReconciler" after the test ends 89 | // Call this function at the start of each of your tests. 90 | func SetupTest(ctx context.Context) *corev1.Namespace { 91 | var stopCh chan struct{} 92 | ns := &corev1.Namespace{} 93 | 94 | BeforeEach(func() { 95 | stopCh = make(chan struct{}) 96 | *ns = corev1.Namespace{ 97 | ObjectMeta: metav1.ObjectMeta{Name: "testns-" + randStringRunes(5)}, 98 | } 99 | 100 | err := k8sClient.Create(ctx, ns) 101 | Expect(err).NotTo(HaveOccurred(), "failed to create test namespace") 102 | 103 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{}) 104 | Expect(err).NotTo(HaveOccurred(), "failed to create manager") 105 | 106 | controller := &FooReconciler{ 107 | Client: mgr.GetClient(), 108 | Scheme: scheme.Scheme, 109 | Log: logf.Log, 110 | Recorder: mgr.GetEventRecorderFor("mykind-controller"), 111 | } 112 | err = controller.SetupWithManager(mgr) 113 | Expect(err).NotTo(HaveOccurred(), "failed to setup controller") 114 | 115 | go func() { 116 | err := mgr.Start(stopCh) 117 | Expect(err).NotTo(HaveOccurred(), "failed to start manager") 118 | }() 119 | }) 120 | 121 | AfterEach(func() { 122 | close(stopCh) 123 | 124 | err := k8sClient.Delete(ctx, ns) 125 | Expect(err).NotTo(HaveOccurred(), "failed to delete test namespace") 126 | }) 127 | 128 | return ns 129 | } 130 | 131 | func init() { 132 | rand.Seed(time.Now().UnixNano()) 133 | } 134 | 135 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz1234567890") 136 | 137 | func randStringRunes(n int) string { 138 | b := make([]rune, n) 139 | for i := range b { 140 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 141 | } 142 | return string(b) 143 | } 144 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/govargo/sample-controller-kubebuilder 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/go-logr/logr v0.1.0 7 | github.com/onsi/ginkgo v1.8.0 8 | github.com/onsi/gomega v1.5.0 9 | k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b 10 | k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d 11 | k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible 12 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 13 | sigs.k8s.io/controller-runtime v0.2.0 14 | sigs.k8s.io/controller-tools v0.2.0 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= 2 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 4 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= 9 | github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 10 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 11 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 12 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 13 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 14 | github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= 15 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 16 | github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= 17 | github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= 18 | github.com/gobuffalo/flect v0.1.5 h1:xpKq9ap8MbYfhuPCF0dBH854Gp9CxZjr/IocxELFflo= 19 | github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= 20 | github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 21 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 22 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 23 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 24 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE= 25 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 26 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 27 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 28 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 29 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 30 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 31 | github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= 32 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 33 | github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 h1:UnszMmmmm5vLwWzDjTFVIkfhvWF1NdrmChl8L2NUDCw= 34 | github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 35 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 36 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 37 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 38 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 39 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 40 | github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= 41 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 42 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= 43 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 44 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 45 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 46 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 47 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 48 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 49 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 50 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 51 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 52 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 53 | github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= 54 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 55 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 56 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 57 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 58 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 59 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 60 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 61 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 62 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 63 | github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= 64 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 65 | github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= 66 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 67 | github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= 68 | github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 69 | github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= 70 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 71 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c h1:MUyE44mTvnI5A0xrxIxaMqoWFzPfQvtE2IWUollMDMs= 72 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= 73 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 74 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 75 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 76 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 77 | github.com/prometheus/client_golang v0.9.0 h1:tXuTFVHC03mW0D+Ua1Q2d1EAVqLTuggX50V0VLICCzY= 78 | github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 79 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 80 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 81 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= 82 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 83 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= 84 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 85 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 86 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 87 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 88 | github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= 89 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 90 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 91 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 92 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 93 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 94 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 95 | go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= 96 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 97 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 98 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 99 | go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= 100 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 101 | golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg= 102 | golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 103 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 104 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 105 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= 106 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 107 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 108 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYRuq8JQ1aa7LJt8EXVyo= 109 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 110 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= 111 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 112 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 113 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 114 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 115 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 116 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 117 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 118 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 119 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= 120 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 121 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 122 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 123 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 124 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 125 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= 126 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 127 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 128 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 129 | golang.org/x/tools v0.0.0-20190501045030-23463209683d h1:D7DVZUZEUgsSIDTivnUtVeGfN5AvhDIKtdIZAqx0ieE= 130 | golang.org/x/tools v0.0.0-20190501045030-23463209683d/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 131 | gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= 132 | gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= 133 | google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= 134 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 135 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 136 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 137 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 138 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 139 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 140 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 141 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 142 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 143 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 144 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 145 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 146 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 147 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 148 | k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo= 149 | k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 150 | k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 h1:q1Qvjzs/iEdXF6A1a8H3AKVFDzJNcJn3nXMs6R6qFtA= 151 | k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= 152 | k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA= 153 | k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 154 | k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ= 155 | k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 156 | k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 157 | k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= 158 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 159 | k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c h1:3KSCztE7gPitlZmWbNwue/2U0YruD65DqX3INopDAQM= 160 | k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 161 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y= 162 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 163 | sigs.k8s.io/controller-runtime v0.2.0 h1:5gL30PXOisGZl+Osi4CmLhvMUj77BO3wJeouKF2va50= 164 | sigs.k8s.io/controller-runtime v0.2.0/go.mod h1:ZHqrRDZi3f6BzONcvlUxkqCKgwasGk5FZrnSv9TVZF4= 165 | sigs.k8s.io/controller-tools v0.2.0 h1:AmQ/0JKBJAjyAiPAkrAf9QW06jkx2lc5hpxMjamsFpw= 166 | sigs.k8s.io/controller-tools v0.2.0/go.mod h1:8t/X+FVWvk6TaBcsa+UKUBbn7GMtvyBKX30SGl4em6Y= 167 | sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs= 168 | sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= 169 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 170 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 171 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "os" 21 | "time" 22 | 23 | samplecontrollerv1alpha1 "github.com/govargo/sample-controller-kubebuilder/api/v1alpha1" 24 | "github.com/govargo/sample-controller-kubebuilder/controllers" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 27 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 28 | ctrl "sigs.k8s.io/controller-runtime" 29 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 30 | // +kubebuilder:scaffold:imports 31 | ) 32 | 33 | var ( 34 | scheme = runtime.NewScheme() 35 | setupLog = ctrl.Log.WithName("setup") 36 | ) 37 | 38 | func init() { 39 | _ = clientgoscheme.AddToScheme(scheme) 40 | 41 | _ = samplecontrollerv1alpha1.AddToScheme(scheme) 42 | // +kubebuilder:scaffold:scheme 43 | } 44 | 45 | func main() { 46 | var metricsAddr string 47 | var enableLeaderElection bool 48 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 49 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 50 | "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") 51 | flag.Parse() 52 | 53 | ctrl.SetLogger(zap.Logger(true)) 54 | var resyncPeriod = time.Second * 30 55 | 56 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 57 | SyncPeriod: &resyncPeriod, 58 | Scheme: scheme, 59 | MetricsBindAddress: metricsAddr, 60 | LeaderElection: enableLeaderElection, 61 | }) 62 | if err != nil { 63 | setupLog.Error(err, "unable to start manager") 64 | os.Exit(1) 65 | } 66 | 67 | if err = (&controllers.FooReconciler{ 68 | Client: mgr.GetClient(), 69 | Log: ctrl.Log.WithName("controllers").WithName("Foo"), 70 | Scheme: mgr.GetScheme(), 71 | Recorder: mgr.GetEventRecorderFor("foo-controller"), 72 | }).SetupWithManager(mgr); err != nil { 73 | setupLog.Error(err, "unable to create controller", "controller", "Foo") 74 | os.Exit(1) 75 | } 76 | // +kubebuilder:scaffold:builder 77 | 78 | setupLog.Info("starting manager") 79 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 80 | setupLog.Error(err, "problem running manager") 81 | os.Exit(1) 82 | } 83 | } 84 | --------------------------------------------------------------------------------