├── config ├── webhook │ ├── manifests.yaml │ ├── kustomization.yaml │ ├── service.yaml │ └── kustomizeconfig.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── certmanager │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── certificate.yaml ├── samples │ └── mygroup_v1beta1_mykind.yaml ├── rbac │ ├── role_binding.yaml │ ├── auth_proxy_role_binding.yaml │ ├── leader_election_role_binding.yaml │ ├── auth_proxy_role.yaml │ ├── kustomization.yaml │ ├── auth_proxy_service.yaml │ ├── leader_election_role.yaml │ └── role.yaml ├── default │ ├── manager_image_patch.yaml │ ├── manager_prometheus_metrics_patch.yaml │ ├── manager_webhook_patch.yaml │ ├── webhookcainjection_patch.yaml │ ├── manager_auth_proxy_patch.yaml │ └── kustomization.yaml └── crd │ ├── patches │ ├── cainjection_in_mykinds.yaml │ └── webhook_in_mykinds.yaml │ ├── kustomizeconfig.yaml │ ├── kustomization.yaml │ └── bases │ └── mygroup.k8s.io_mykinds.yaml ├── PROJECT ├── .gitignore ├── go.mod ├── hack └── boilerplate.go.txt ├── Dockerfile ├── api └── v1beta1 │ ├── groupversion_info.go │ ├── mykind_types.go │ └── zz_generated.deepcopy.go ├── Makefile ├── main.go ├── controllers ├── suite_test.go ├── mykind_controller_test.go └── mykind_controller.go └── go.sum /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | version: "2" 2 | domain: k8s.io 3 | repo: jetstack.io/example-controller 4 | resources: 5 | - group: mygroup 6 | version: v1beta1 7 | kind: MyKind 8 | -------------------------------------------------------------------------------- /config/samples/mygroup_v1beta1_mykind.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: mygroup.k8s.io/v1beta1 2 | kind: MyKind 3 | metadata: 4 | name: mykind-sample 5 | spec: 6 | # Add fields here 7 | foo: bar 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/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/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/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/default/manager_image_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 | # Change the value of image field below to your controller image URL 11 | - image: eu.gcr.io/jetstack-james/example-controller:0.1 12 | name: manager 13 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_mykinds.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: mykinds.mygroup.k8s.io 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Kubernetes Generated files - skip generated files, except for vendored files 17 | 18 | !vendor/**/zz_generated.* 19 | 20 | # editor and IDE paraphernalia 21 | .idea 22 | *.swp 23 | *.swo 24 | *~ 25 | -------------------------------------------------------------------------------- /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: controller-manager-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/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 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module jetstack.io/example-controller 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-beta.3 14 | sigs.k8s.io/controller-tools v0.2.0-beta.3 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /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/crd/patches/webhook_in_mykinds.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: mykinds.mygroup.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/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/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 | - mygroup.k8s.io 11 | resources: 12 | - mykinds 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - mygroup.k8s.io 23 | resources: 24 | - mykinds/status 25 | verbs: 26 | - get 27 | - patch 28 | - update 29 | - apiGroups: 30 | - apps 31 | resources: 32 | - deployments 33 | verbs: 34 | - create 35 | - delete 36 | - get 37 | - list 38 | - update 39 | - watch 40 | - apiGroups: 41 | - "" 42 | resources: 43 | - events 44 | verbs: 45 | - create 46 | - patch 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/mygroup.k8s.io_mykinds.yaml 6 | # +kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patches: 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_mykinds.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_mykinds.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 | -------------------------------------------------------------------------------- /api/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains API Schema definitions for the mygroup v1beta1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=mygroup.k8s.io 20 | package v1beta1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "mygroup.k8s.io", Version: "v1beta1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /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 ./api/... ./controllers/... -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 26 | go run ./main.go 27 | 28 | # Install CRDs into a cluster 29 | install: manifests 30 | kubectl apply -f config/crd/bases 31 | 32 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 33 | deploy: manifests 34 | kubectl apply -f config/crd/bases 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=./api/... 52 | 53 | # Build the docker image 54 | docker-build: test 55 | docker build . -t ${IMG} 56 | @echo "updating kustomize image patch file for manager resource" 57 | sed -i'' -e 's@image: .*@image: '"${IMG}"'@' ./config/default/manager_image_patch.yaml 58 | 59 | # Push the docker image 60 | docker-push: 61 | docker push ${IMG} 62 | 63 | # find or download controller-gen 64 | # download controller-gen if necessary 65 | controller-gen: 66 | ifeq (, $(shell which controller-gen)) 67 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.0-beta.3 68 | CONTROLLER_GEN=$(GOBIN)/controller-gen 69 | else 70 | CONTROLLER_GEN=$(shell which controller-gen) 71 | endif 72 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | 23 | mygroupv1beta1 "jetstack.io/example-controller/api/v1beta1" 24 | "jetstack.io/example-controller/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 | _ = mygroupv1beta1.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 | 55 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 56 | Scheme: scheme, 57 | MetricsBindAddress: metricsAddr, 58 | LeaderElection: enableLeaderElection, 59 | }) 60 | if err != nil { 61 | setupLog.Error(err, "unable to start manager") 62 | os.Exit(1) 63 | } 64 | 65 | if err = (&controllers.MyKindReconciler{ 66 | Client: mgr.GetClient(), 67 | Log: ctrl.Log.WithName("controllers").WithName("MyKind"), 68 | Recorder: mgr.GetEventRecorderFor("mykind-controller"), 69 | }).SetupWithManager(mgr); err != nil { 70 | setupLog.Error(err, "unable to create controller", "controller", "MyKind") 71 | os.Exit(1) 72 | } 73 | // +kubebuilder:scaffold:builder 74 | 75 | setupLog.Info("starting manager") 76 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 77 | setupLog.Error(err, "problem running manager") 78 | os.Exit(1) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /api/v1beta1/mykind_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // MyKindSpec defines the desired state of MyKind 27 | type MyKindSpec struct { 28 | // Important: Run "make" to regenerate code after modifying this file 29 | 30 | // DeploymentName is the name of the Deployment resource that the 31 | // controller should create. 32 | // This field must be specified. 33 | // +kubebuilder:validation:MaxLength=64 34 | DeploymentName string `json:"deploymentName"` 35 | 36 | // Replicas is the number of replicas that should be specified on the 37 | // Deployment resource that the controller creates. 38 | // If not specified, one replica will be created. 39 | // +optional 40 | // +kubebuilder:validation:Minimum=0 41 | Replicas *int32 `json:"replicas,omitempty"` 42 | } 43 | 44 | // MyKindStatus defines the observed state of MyKind 45 | type MyKindStatus struct { 46 | // Important: Run "make" to regenerate code after modifying this file 47 | 48 | // ReadyReplicas is the number of 'ready' replicas observed on the 49 | // Deployment resource created for this MyKind resource. 50 | // +optional 51 | // +kubebuilder:validation:Minimum=0 52 | ReadyReplicas int32 `json:"readyReplicas,omitempty"` 53 | } 54 | 55 | // +kubebuilder:object:root=true 56 | // +kubebuilder:subresource:status 57 | 58 | // MyKind is the Schema for the mykinds API 59 | type MyKind struct { 60 | metav1.TypeMeta `json:",inline"` 61 | metav1.ObjectMeta `json:"metadata,omitempty"` 62 | 63 | Spec MyKindSpec `json:"spec,omitempty"` 64 | Status MyKindStatus `json:"status,omitempty"` 65 | } 66 | 67 | // +kubebuilder:object:root=true 68 | 69 | // MyKindList contains a list of MyKind 70 | type MyKindList struct { 71 | metav1.TypeMeta `json:",inline"` 72 | metav1.ListMeta `json:"metadata,omitempty"` 73 | Items []MyKind `json:"items"` 74 | } 75 | 76 | func init() { 77 | SchemeBuilder.Register(&MyKind{}, &MyKindList{}) 78 | } 79 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: example-controller-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: example-controller- 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 | patches: 25 | - manager_image_patch.yaml 26 | # Protect the /metrics endpoint by putting it behind auth. 27 | # Only one of manager_auth_proxy_patch.yaml and 28 | # manager_prometheus_metrics_patch.yaml should be enabled. 29 | - manager_auth_proxy_patch.yaml 30 | # If you want your controller-manager to expose the /metrics 31 | # endpoint w/o any authn/z, uncomment the following line and 32 | # comment manager_auth_proxy_patch.yaml. 33 | # Only one of manager_auth_proxy_patch.yaml and 34 | # manager_prometheus_metrics_patch.yaml should be enabled. 35 | #- manager_prometheus_metrics_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml 38 | #- manager_webhook_patch.yaml 39 | 40 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 41 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 42 | # 'CERTMANAGER' needs to be enabled to use ca injection 43 | #- webhookcainjection_patch.yaml 44 | 45 | # the following config is for teaching kustomize how to do var substitution 46 | vars: 47 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 48 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 49 | # objref: 50 | # kind: Certificate 51 | # group: certmanager.k8s.io 52 | # version: v1alpha1 53 | # name: serving-cert # this name should match the one in certificate.yaml 54 | # fieldref: 55 | # fieldpath: metadata.namespace 56 | #- name: CERTIFICATE_NAME 57 | # objref: 58 | # kind: Certificate 59 | # group: certmanager.k8s.io 60 | # version: v1alpha1 61 | # name: serving-cert # this name should match the one in certificate.yaml 62 | #- name: SERVICE_NAMESPACE # namespace of the service 63 | # objref: 64 | # kind: Service 65 | # version: v1 66 | # name: webhook-service 67 | # fieldref: 68 | # fieldpath: metadata.namespace 69 | #- name: SERVICE_NAME 70 | # objref: 71 | # kind: Service 72 | # version: v1 73 | # name: webhook-service 74 | -------------------------------------------------------------------------------- /api/v1beta1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2019 The Kubernetes Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // autogenerated by controller-gen object, do not modify manually 20 | 21 | package v1beta1 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in *MyKind) DeepCopyInto(out *MyKind) { 29 | *out = *in 30 | out.TypeMeta = in.TypeMeta 31 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 32 | in.Spec.DeepCopyInto(&out.Spec) 33 | out.Status = in.Status 34 | } 35 | 36 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyKind. 37 | func (in *MyKind) DeepCopy() *MyKind { 38 | if in == nil { 39 | return nil 40 | } 41 | out := new(MyKind) 42 | in.DeepCopyInto(out) 43 | return out 44 | } 45 | 46 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 47 | func (in *MyKind) DeepCopyObject() runtime.Object { 48 | if c := in.DeepCopy(); c != nil { 49 | return c 50 | } 51 | return nil 52 | } 53 | 54 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 55 | func (in *MyKindList) DeepCopyInto(out *MyKindList) { 56 | *out = *in 57 | out.TypeMeta = in.TypeMeta 58 | out.ListMeta = in.ListMeta 59 | if in.Items != nil { 60 | in, out := &in.Items, &out.Items 61 | *out = make([]MyKind, len(*in)) 62 | for i := range *in { 63 | (*in)[i].DeepCopyInto(&(*out)[i]) 64 | } 65 | } 66 | } 67 | 68 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyKindList. 69 | func (in *MyKindList) DeepCopy() *MyKindList { 70 | if in == nil { 71 | return nil 72 | } 73 | out := new(MyKindList) 74 | in.DeepCopyInto(out) 75 | return out 76 | } 77 | 78 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 79 | func (in *MyKindList) DeepCopyObject() runtime.Object { 80 | if c := in.DeepCopy(); c != nil { 81 | return c 82 | } 83 | return nil 84 | } 85 | 86 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 87 | func (in *MyKindSpec) DeepCopyInto(out *MyKindSpec) { 88 | *out = *in 89 | if in.Replicas != nil { 90 | in, out := &in.Replicas, &out.Replicas 91 | *out = new(int32) 92 | **out = **in 93 | } 94 | } 95 | 96 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyKindSpec. 97 | func (in *MyKindSpec) DeepCopy() *MyKindSpec { 98 | if in == nil { 99 | return nil 100 | } 101 | out := new(MyKindSpec) 102 | in.DeepCopyInto(out) 103 | return out 104 | } 105 | 106 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 107 | func (in *MyKindStatus) DeepCopyInto(out *MyKindStatus) { 108 | *out = *in 109 | } 110 | 111 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyKindStatus. 112 | func (in *MyKindStatus) DeepCopy() *MyKindStatus { 113 | if in == nil { 114 | return nil 115 | } 116 | out := new(MyKindStatus) 117 | in.DeepCopyInto(out) 118 | return out 119 | } 120 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "math/rand" 22 | "path/filepath" 23 | "testing" 24 | "time" 25 | 26 | . "github.com/onsi/ginkgo" 27 | . "github.com/onsi/gomega" 28 | core "k8s.io/api/core/v1" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | "k8s.io/client-go/kubernetes/scheme" 31 | "k8s.io/client-go/rest" 32 | ctrl "sigs.k8s.io/controller-runtime" 33 | "sigs.k8s.io/controller-runtime/pkg/client" 34 | "sigs.k8s.io/controller-runtime/pkg/envtest" 35 | logf "sigs.k8s.io/controller-runtime/pkg/log" 36 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 37 | // +kubebuilder:scaffold:imports 38 | 39 | mygroupv1beta1 "jetstack.io/example-controller/api/v1beta1" 40 | ) 41 | 42 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 43 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 44 | 45 | var cfg *rest.Config 46 | var k8sClient client.Client 47 | var testEnv *envtest.Environment 48 | 49 | func TestAPIs(t *testing.T) { 50 | RegisterFailHandler(Fail) 51 | 52 | RunSpecsWithDefaultAndCustomReporters(t, 53 | "Controller Suite", 54 | []Reporter{envtest.NewlineReporter{}}) 55 | } 56 | 57 | var _ = BeforeSuite(func(done Done) { 58 | logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) 59 | 60 | By("bootstrapping test environment") 61 | testEnv = &envtest.Environment{ 62 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 63 | } 64 | 65 | var err error 66 | cfg, err = testEnv.Start() 67 | Expect(err).ToNot(HaveOccurred()) 68 | Expect(cfg).ToNot(BeNil()) 69 | 70 | err = mygroupv1beta1.AddToScheme(scheme.Scheme) 71 | Expect(err).NotTo(HaveOccurred()) 72 | 73 | // +kubebuilder:scaffold:scheme 74 | 75 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 76 | Expect(err).ToNot(HaveOccurred()) 77 | Expect(k8sClient).ToNot(BeNil()) 78 | 79 | close(done) 80 | }, 60) 81 | 82 | var _ = AfterSuite(func() { 83 | By("tearing down the test environment") 84 | err := testEnv.Stop() 85 | Expect(err).ToNot(HaveOccurred()) 86 | }) 87 | 88 | // SetupTest will set up a testing environment. 89 | // This includes: 90 | // * creating a Namespace to be used during the test 91 | // * starting the 'MyKindReconciler' 92 | // * stopping the 'MyKindReconciler" after the test ends 93 | // Call this function at the start of each of your tests. 94 | func SetupTest(ctx context.Context) *core.Namespace { 95 | var stopCh chan struct{} 96 | ns := &core.Namespace{} 97 | 98 | BeforeEach(func() { 99 | stopCh = make(chan struct{}) 100 | *ns = core.Namespace{ 101 | ObjectMeta: metav1.ObjectMeta{Name: "testns-" + randStringRunes(5)}, 102 | } 103 | 104 | err := k8sClient.Create(ctx, ns) 105 | Expect(err).NotTo(HaveOccurred(), "failed to create test namespace") 106 | 107 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{}) 108 | Expect(err).NotTo(HaveOccurred(), "failed to create manager") 109 | 110 | controller := &MyKindReconciler{ 111 | Client: mgr.GetClient(), 112 | Log: logf.Log, 113 | Recorder: mgr.GetEventRecorderFor("mykind-controller"), 114 | } 115 | err = controller.SetupWithManager(mgr) 116 | Expect(err).NotTo(HaveOccurred(), "failed to setup controller") 117 | 118 | go func() { 119 | err := mgr.Start(stopCh) 120 | Expect(err).NotTo(HaveOccurred(), "failed to start manager") 121 | }() 122 | }) 123 | 124 | AfterEach(func() { 125 | close(stopCh) 126 | 127 | err := k8sClient.Delete(ctx, ns) 128 | Expect(err).NotTo(HaveOccurred(), "failed to delete test namespace") 129 | }) 130 | 131 | return ns 132 | } 133 | 134 | func init() { 135 | rand.Seed(time.Now().UnixNano()) 136 | } 137 | 138 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz1234567890") 139 | 140 | func randStringRunes(n int) string { 141 | b := make([]rune, n) 142 | for i := range b { 143 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 144 | } 145 | return string(b) 146 | } 147 | -------------------------------------------------------------------------------- /controllers/mykind_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | apps "k8s.io/api/apps/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | "k8s.io/utils/pointer" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | 31 | mygroupv1beta1 "jetstack.io/example-controller/api/v1beta1" 32 | ) 33 | 34 | var _ = Context("Inside of a new namespace", func() { 35 | ctx := context.TODO() 36 | ns := SetupTest(ctx) 37 | 38 | Describe("when no existing resources exist", func() { 39 | 40 | It("should create a new Deployment resource with the specified name and one replica if none is provided", func() { 41 | myKind := &mygroupv1beta1.MyKind{ 42 | ObjectMeta: metav1.ObjectMeta{ 43 | Name: "testresource", 44 | Namespace: ns.Name, 45 | }, 46 | Spec: mygroupv1beta1.MyKindSpec{ 47 | DeploymentName: "deployment-name", 48 | }, 49 | } 50 | 51 | err := k8sClient.Create(ctx, myKind) 52 | Expect(err).NotTo(HaveOccurred(), "failed to create test MyKind resource") 53 | 54 | deployment := &apps.Deployment{} 55 | Eventually( 56 | getResourceFunc(ctx, client.ObjectKey{Name: "deployment-name", Namespace: myKind.Namespace}, deployment), 57 | time.Second*5, time.Millisecond*500).Should(BeNil()) 58 | 59 | Expect(*deployment.Spec.Replicas).To(Equal(int32(1))) 60 | }) 61 | 62 | It("should create a new Deployment resource with the specified name and two replicas if two is specified", func() { 63 | myKind := &mygroupv1beta1.MyKind{ 64 | ObjectMeta: metav1.ObjectMeta{ 65 | Name: "testresource", 66 | Namespace: ns.Name, 67 | }, 68 | Spec: mygroupv1beta1.MyKindSpec{ 69 | DeploymentName: "deployment-name", 70 | Replicas: pointer.Int32Ptr(2), 71 | }, 72 | } 73 | 74 | err := k8sClient.Create(ctx, myKind) 75 | Expect(err).NotTo(HaveOccurred(), "failed to create test MyKind resource") 76 | 77 | deployment := &apps.Deployment{} 78 | Eventually( 79 | getResourceFunc(ctx, client.ObjectKey{Name: "deployment-name", Namespace: myKind.Namespace}, deployment), 80 | time.Second*5, time.Millisecond*500).Should(BeNil()) 81 | 82 | Expect(*deployment.Spec.Replicas).To(Equal(int32(2))) 83 | }) 84 | 85 | It("should allow updating the replicas count after creating a MyKind resource", func() { 86 | deploymentObjectKey := client.ObjectKey{ 87 | Name: "deployment-name", 88 | Namespace: ns.Name, 89 | } 90 | myKindObjectKey := client.ObjectKey{ 91 | Name: "testresource", 92 | Namespace: ns.Name, 93 | } 94 | myKind := &mygroupv1beta1.MyKind{ 95 | ObjectMeta: metav1.ObjectMeta{ 96 | Name: myKindObjectKey.Name, 97 | Namespace: myKindObjectKey.Namespace, 98 | }, 99 | Spec: mygroupv1beta1.MyKindSpec{ 100 | DeploymentName: deploymentObjectKey.Name, 101 | }, 102 | } 103 | 104 | err := k8sClient.Create(ctx, myKind) 105 | Expect(err).NotTo(HaveOccurred(), "failed to create test MyKind resource") 106 | 107 | deployment := &apps.Deployment{} 108 | Eventually( 109 | getResourceFunc(ctx, deploymentObjectKey, deployment), 110 | time.Second*5, time.Millisecond*500).Should(BeNil(), "deployment resource should exist") 111 | 112 | Expect(*deployment.Spec.Replicas).To(Equal(int32(1)), "replica count should be equal to 1") 113 | 114 | err = k8sClient.Get(ctx, myKindObjectKey, myKind) 115 | Expect(err).NotTo(HaveOccurred(), "failed to retrieve MyKind resource") 116 | 117 | myKind.Spec.Replicas = pointer.Int32Ptr(2) 118 | err = k8sClient.Update(ctx, myKind) 119 | Expect(err).NotTo(HaveOccurred(), "failed to Update MyKind resource") 120 | 121 | Eventually(getDeploymentReplicasFunc(ctx, deploymentObjectKey)). 122 | Should(Equal(int32(2)), "expected Deployment resource to be scale to 2 replicas") 123 | }) 124 | 125 | It("should clean up an old Deployment resource if the deploymentName is changed", func() { 126 | deploymentObjectKey := client.ObjectKey{ 127 | Name: "deployment-name", 128 | Namespace: ns.Name, 129 | } 130 | newDeploymentObjectKey := client.ObjectKey{ 131 | Name: "new-deployment", 132 | Namespace: ns.Name, 133 | } 134 | myKindObjectKey := client.ObjectKey{ 135 | Name: "testresource", 136 | Namespace: ns.Name, 137 | } 138 | myKind := &mygroupv1beta1.MyKind{ 139 | ObjectMeta: metav1.ObjectMeta{ 140 | Name: myKindObjectKey.Name, 141 | Namespace: myKindObjectKey.Namespace, 142 | }, 143 | Spec: mygroupv1beta1.MyKindSpec{ 144 | DeploymentName: deploymentObjectKey.Name, 145 | }, 146 | } 147 | 148 | err := k8sClient.Create(ctx, myKind) 149 | Expect(err).NotTo(HaveOccurred(), "failed to create test MyKind resource") 150 | 151 | deployment := &apps.Deployment{} 152 | Eventually( 153 | getResourceFunc(ctx, deploymentObjectKey, deployment), 154 | time.Second*5, time.Millisecond*500).Should(BeNil(), "deployment resource should exist") 155 | 156 | err = k8sClient.Get(ctx, myKindObjectKey, myKind) 157 | Expect(err).NotTo(HaveOccurred(), "failed to retrieve MyKind resource") 158 | 159 | myKind.Spec.DeploymentName = newDeploymentObjectKey.Name 160 | err = k8sClient.Update(ctx, myKind) 161 | Expect(err).NotTo(HaveOccurred(), "failed to Update MyKind resource") 162 | 163 | Eventually( 164 | getResourceFunc(ctx, deploymentObjectKey, deployment), 165 | time.Second*5, time.Millisecond*500).ShouldNot(BeNil(), "old deployment resource should be deleted") 166 | 167 | Eventually( 168 | getResourceFunc(ctx, newDeploymentObjectKey, deployment), 169 | time.Second*5, time.Millisecond*500).Should(BeNil(), "new deployment resource should be created") 170 | }) 171 | }) 172 | }) 173 | 174 | func getResourceFunc(ctx context.Context, key client.ObjectKey, obj runtime.Object) func() error { 175 | return func() error { 176 | return k8sClient.Get(ctx, key, obj) 177 | } 178 | } 179 | 180 | func getDeploymentReplicasFunc(ctx context.Context, key client.ObjectKey) func() int32 { 181 | return func() int32 { 182 | depl := &apps.Deployment{} 183 | err := k8sClient.Get(ctx, key, depl) 184 | Expect(err).NotTo(HaveOccurred(), "failed to get Deployment resource") 185 | 186 | return *depl.Spec.Replicas 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /controllers/mykind_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/go-logr/logr" 23 | apps "k8s.io/api/apps/v1" 24 | core "k8s.io/api/core/v1" 25 | apierrors "k8s.io/apimachinery/pkg/api/errors" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | "k8s.io/client-go/tools/record" 29 | ctrl "sigs.k8s.io/controller-runtime" 30 | "sigs.k8s.io/controller-runtime/pkg/client" 31 | 32 | mygroupv1beta1 "jetstack.io/example-controller/api/v1beta1" 33 | ) 34 | 35 | // MyKindReconciler reconciles a MyKind object 36 | type MyKindReconciler struct { 37 | client.Client 38 | Log logr.Logger 39 | 40 | Recorder record.EventRecorder 41 | } 42 | 43 | // +kubebuilder:rbac:groups=mygroup.k8s.io,resources=mykinds,verbs=get;list;watch;create;update;patch;delete 44 | // +kubebuilder:rbac:groups=mygroup.k8s.io,resources=mykinds/status,verbs=get;update;patch 45 | // +kubebuilder:rbac:groups=mygroup.k8s.io,resources=mykinds/status,verbs=get;update;patch 46 | // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;delete 47 | // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch 48 | 49 | func (r *MyKindReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 50 | ctx := context.Background() 51 | log := r.Log.WithValues("mykind", req.NamespacedName) 52 | 53 | // your logic here 54 | log.Info("fetching MyKind resource") 55 | myKind := mygroupv1beta1.MyKind{} 56 | if err := r.Client.Get(ctx, req.NamespacedName, &myKind); err != nil { 57 | log.Error(err, "failed to get MyKind resource") 58 | // Ignore NotFound errors as they will be retried automatically if the 59 | // resource is created in future. 60 | return ctrl.Result{}, client.IgnoreNotFound(err) 61 | } 62 | 63 | if err := r.cleanupOwnedResources(ctx, log, &myKind); err != nil { 64 | log.Error(err, "failed to clean up old Deployment resources for this MyKind") 65 | return ctrl.Result{}, err 66 | } 67 | 68 | log = log.WithValues("deployment_name", myKind.Spec.DeploymentName) 69 | 70 | log.Info("checking if an existing Deployment exists for this resource") 71 | deployment := apps.Deployment{} 72 | err := r.Client.Get(ctx, client.ObjectKey{Namespace: myKind.Namespace, Name: myKind.Spec.DeploymentName}, &deployment) 73 | if apierrors.IsNotFound(err) { 74 | log.Info("could not find existing Deployment for MyKind, creating one...") 75 | 76 | deployment = *buildDeployment(myKind) 77 | if err := r.Client.Create(ctx, &deployment); err != nil { 78 | log.Error(err, "failed to create Deployment resource") 79 | return ctrl.Result{}, err 80 | } 81 | 82 | r.Recorder.Eventf(&myKind, core.EventTypeNormal, "Created", "Created deployment %q", deployment.Name) 83 | log.Info("created Deployment resource for MyKind") 84 | return ctrl.Result{}, nil 85 | } 86 | if err != nil { 87 | log.Error(err, "failed to get Deployment for MyKind resource") 88 | return ctrl.Result{}, err 89 | } 90 | 91 | log.Info("existing Deployment resource already exists for MyKind, checking replica count") 92 | 93 | expectedReplicas := int32(1) 94 | if myKind.Spec.Replicas != nil { 95 | expectedReplicas = *myKind.Spec.Replicas 96 | } 97 | if *deployment.Spec.Replicas != expectedReplicas { 98 | log.Info("updating replica count", "old_count", *deployment.Spec.Replicas, "new_count", expectedReplicas) 99 | 100 | deployment.Spec.Replicas = &expectedReplicas 101 | if err := r.Client.Update(ctx, &deployment); err != nil { 102 | log.Error(err, "failed to Deployment update replica count") 103 | return ctrl.Result{}, err 104 | } 105 | 106 | r.Recorder.Eventf(&myKind, core.EventTypeNormal, "Scaled", "Scaled deployment %q to %d replicas", deployment.Name, expectedReplicas) 107 | 108 | return ctrl.Result{}, nil 109 | } 110 | 111 | log.Info("replica count up to date", "replica_count", *deployment.Spec.Replicas) 112 | 113 | log.Info("updating MyKind resource status") 114 | myKind.Status.ReadyReplicas = deployment.Status.ReadyReplicas 115 | if r.Client.Status().Update(ctx, &myKind); err != nil { 116 | log.Error(err, "failed to update MyKind status") 117 | return ctrl.Result{}, err 118 | } 119 | 120 | log.Info("resource status synced") 121 | 122 | return ctrl.Result{}, nil 123 | } 124 | 125 | // cleanupOwnedResources will Delete any existing Deployment resources that 126 | // were created for the given MyKind that no longer match the 127 | // myKind.spec.deploymentName field. 128 | func (r *MyKindReconciler) cleanupOwnedResources(ctx context.Context, log logr.Logger, myKind *mygroupv1beta1.MyKind) error { 129 | log.Info("finding existing Deployments for MyKind resource") 130 | 131 | // List all deployment resources owned by this MyKind 132 | var deployments apps.DeploymentList 133 | if err := r.List(ctx, &deployments, client.InNamespace(myKind.Namespace), client.MatchingField(deploymentOwnerKey, myKind.Name)); err != nil { 134 | return err 135 | } 136 | 137 | deleted := 0 138 | for _, depl := range deployments.Items { 139 | if depl.Name == myKind.Spec.DeploymentName { 140 | // If this deployment's name matches the one on the MyKind resource 141 | // then do not delete it. 142 | continue 143 | } 144 | 145 | if err := r.Client.Delete(ctx, &depl); err != nil { 146 | log.Error(err, "failed to delete Deployment resource") 147 | return err 148 | } 149 | 150 | r.Recorder.Eventf(myKind, core.EventTypeNormal, "Deleted", "Deleted deployment %q", depl.Name) 151 | deleted++ 152 | } 153 | 154 | log.Info("finished cleaning up old Deployment resources", "number_deleted", deleted) 155 | 156 | return nil 157 | } 158 | 159 | func buildDeployment(myKind mygroupv1beta1.MyKind) *apps.Deployment { 160 | deployment := apps.Deployment{ 161 | ObjectMeta: metav1.ObjectMeta{ 162 | Name: myKind.Spec.DeploymentName, 163 | Namespace: myKind.Namespace, 164 | OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&myKind, mygroupv1beta1.GroupVersion.WithKind("MyKind"))}, 165 | }, 166 | Spec: apps.DeploymentSpec{ 167 | Replicas: myKind.Spec.Replicas, 168 | Selector: &metav1.LabelSelector{ 169 | MatchLabels: map[string]string{ 170 | "example-controller.jetstack.io/deployment-name": myKind.Spec.DeploymentName, 171 | }, 172 | }, 173 | Template: core.PodTemplateSpec{ 174 | ObjectMeta: metav1.ObjectMeta{ 175 | Labels: map[string]string{ 176 | "example-controller.jetstack.io/deployment-name": myKind.Spec.DeploymentName, 177 | }, 178 | }, 179 | Spec: core.PodSpec{ 180 | Containers: []core.Container{ 181 | { 182 | Name: "nginx", 183 | Image: "nginx:latest", 184 | }, 185 | }, 186 | }, 187 | }, 188 | }, 189 | } 190 | return &deployment 191 | } 192 | 193 | var ( 194 | deploymentOwnerKey = ".metadata.controller" 195 | ) 196 | 197 | func (r *MyKindReconciler) SetupWithManager(mgr ctrl.Manager) error { 198 | if err := mgr.GetFieldIndexer().IndexField(&apps.Deployment{}, deploymentOwnerKey, func(rawObj runtime.Object) []string { 199 | // grab the Deployment object, extract the owner... 200 | depl := rawObj.(*apps.Deployment) 201 | owner := metav1.GetControllerOf(depl) 202 | if owner == nil { 203 | return nil 204 | } 205 | // ...make sure it's a MyKind... 206 | if owner.APIVersion != mygroupv1beta1.GroupVersion.String() || owner.Kind != "MyKind" { 207 | return nil 208 | } 209 | 210 | // ...and if so, return it 211 | return []string{owner.Name} 212 | }); err != nil { 213 | return err 214 | } 215 | 216 | return ctrl.NewControllerManagedBy(mgr). 217 | For(&mygroupv1beta1.MyKind{}). 218 | Owns(&apps.Deployment{}). 219 | Complete(r) 220 | } 221 | -------------------------------------------------------------------------------- /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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 11 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 12 | github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= 13 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 14 | github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= 15 | github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= 16 | github.com/gobuffalo/flect v0.1.5 h1:xpKq9ap8MbYfhuPCF0dBH854Gp9CxZjr/IocxELFflo= 17 | github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= 18 | github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 19 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 20 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 21 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 22 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE= 23 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 24 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 25 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 27 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 28 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 29 | github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= 30 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 31 | github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 h1:UnszMmmmm5vLwWzDjTFVIkfhvWF1NdrmChl8L2NUDCw= 32 | github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 33 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 34 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 35 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 36 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 37 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 38 | github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= 39 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 40 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= 41 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 42 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 43 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 44 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 45 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 46 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 47 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 48 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 49 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 50 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 53 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 54 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 55 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 56 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 57 | github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= 58 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 59 | github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= 60 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 61 | github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= 62 | github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 63 | github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= 64 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 65 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c h1:MUyE44mTvnI5A0xrxIxaMqoWFzPfQvtE2IWUollMDMs= 66 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= 67 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 68 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 69 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 70 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 71 | github.com/prometheus/client_golang v0.9.0 h1:tXuTFVHC03mW0D+Ua1Q2d1EAVqLTuggX50V0VLICCzY= 72 | github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 73 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 74 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 75 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= 76 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 77 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= 78 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 79 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 80 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 81 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 82 | github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= 83 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 84 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 85 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 86 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 87 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 88 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 89 | go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= 90 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 91 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 92 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 93 | go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= 94 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 95 | golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg= 96 | golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 97 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 98 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 99 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= 100 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 101 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 102 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYRuq8JQ1aa7LJt8EXVyo= 103 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 104 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= 105 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 106 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 107 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 108 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 109 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 110 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 111 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 112 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= 113 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 114 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 115 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 116 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 117 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 118 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= 119 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 120 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 121 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 122 | golang.org/x/tools v0.0.0-20190501045030-23463209683d h1:D7DVZUZEUgsSIDTivnUtVeGfN5AvhDIKtdIZAqx0ieE= 123 | golang.org/x/tools v0.0.0-20190501045030-23463209683d/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 124 | gomodules.xyz/jsonpatch/v2 v2.0.0 h1:OyHbl+7IOECpPKfVK42oFr6N7+Y2dR+Jsb/IiDV3hOo= 125 | gomodules.xyz/jsonpatch/v2 v2.0.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= 126 | google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= 127 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 128 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 129 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 130 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 131 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 132 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 133 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 134 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 135 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 136 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 137 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 138 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 139 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 140 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 141 | k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo= 142 | k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 143 | k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 h1:q1Qvjzs/iEdXF6A1a8H3AKVFDzJNcJn3nXMs6R6qFtA= 144 | k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= 145 | k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA= 146 | k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 147 | k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ= 148 | k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 149 | k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 150 | k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= 151 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 152 | k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c h1:3KSCztE7gPitlZmWbNwue/2U0YruD65DqX3INopDAQM= 153 | k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 154 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y= 155 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 156 | sigs.k8s.io/controller-runtime v0.2.0-beta.3 h1:K3dddu6/pOVORH2dBOnEbXif6R80oSDa4y/t1jhoh8s= 157 | sigs.k8s.io/controller-runtime v0.2.0-beta.3/go.mod h1:HweyYKQ8fBuzdu2bdaeBJvsFgAi/OqBBnrVGXcqKhME= 158 | sigs.k8s.io/controller-tools v0.2.0-beta.3 h1:7h1Hx+vpg79dktBILuQq/aDed4GZcdxeUwuEb59G1bw= 159 | sigs.k8s.io/controller-tools v0.2.0-beta.3/go.mod h1:bPfk8OXC5AhzPGuPZjcwcltUhQUXYFmUtrJCCF2XaaE= 160 | sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs= 161 | sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= 162 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 163 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 164 | -------------------------------------------------------------------------------- /config/crd/bases/mygroup.k8s.io_mykinds.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | creationTimestamp: null 7 | name: mykinds.mygroup.k8s.io 8 | spec: 9 | group: mygroup.k8s.io 10 | names: 11 | kind: MyKind 12 | plural: mykinds 13 | scope: "" 14 | subresources: 15 | status: {} 16 | validation: 17 | openAPIV3Schema: 18 | description: MyKind is the Schema for the mykinds 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 | description: ObjectMeta is metadata that all persisted resources must have, 32 | which includes all objects users must create. 33 | properties: 34 | annotations: 35 | additionalProperties: 36 | type: string 37 | description: 'Annotations is an unstructured key value map stored with 38 | a resource that may be set by external tools to store and retrieve 39 | arbitrary metadata. They are not queryable and should be preserved 40 | when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' 41 | type: object 42 | clusterName: 43 | description: The name of the cluster which the object belongs to. This 44 | is used to distinguish resources with same name and namespace in different 45 | clusters. This field is not set anywhere right now and apiserver is 46 | going to ignore it if set in create or update request. 47 | type: string 48 | creationTimestamp: 49 | description: "CreationTimestamp is a timestamp representing the server 50 | time when this object was created. It is not guaranteed to be set 51 | in happens-before order across separate operations. Clients may not 52 | set this value. It is represented in RFC3339 form and is in UTC. \n 53 | Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" 54 | format: date-time 55 | type: string 56 | deletionGracePeriodSeconds: 57 | description: Number of seconds allowed for this object to gracefully 58 | terminate before it will be removed from the system. Only set when 59 | deletionTimestamp is also set. May only be shortened. Read-only. 60 | format: int64 61 | type: integer 62 | deletionTimestamp: 63 | description: "DeletionTimestamp is RFC 3339 date and time at which this 64 | resource will be deleted. This field is set by the server when a graceful 65 | deletion is requested by the user, and is not directly settable by 66 | a client. The resource is expected to be deleted (no longer visible 67 | from resource lists, and not reachable by name) after the time in 68 | this field, once the finalizers list is empty. As long as the finalizers 69 | list contains items, deletion is blocked. Once the deletionTimestamp 70 | is set, this value may not be unset or be set further into the future, 71 | although it may be shortened or the resource may be deleted prior 72 | to this time. For example, a user may request that a pod is deleted 73 | in 30 seconds. The Kubelet will react by sending a graceful termination 74 | signal to the containers in the pod. After that 30 seconds, the Kubelet 75 | will send a hard termination signal (SIGKILL) to the container and 76 | after cleanup, remove the pod from the API. In the presence of network 77 | partitions, this object may still exist after this timestamp, until 78 | an administrator or automated process can determine the resource is 79 | fully terminated. If not set, graceful deletion of the object has 80 | not been requested. \n Populated by the system when a graceful deletion 81 | is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" 82 | format: date-time 83 | type: string 84 | finalizers: 85 | description: Must be empty before the object is deleted from the registry. 86 | Each entry is an identifier for the responsible component that will 87 | remove the entry from the list. If the deletionTimestamp of the object 88 | is non-nil, entries in this list can only be removed. 89 | items: 90 | type: string 91 | type: array 92 | generateName: 93 | description: "GenerateName is an optional prefix, used by the server, 94 | to generate a unique name ONLY IF the Name field has not been provided. 95 | If this field is used, the name returned to the client will be different 96 | than the name passed. This value will also be combined with a unique 97 | suffix. The provided value has the same validation rules as the Name 98 | field, and may be truncated by the length of the suffix required to 99 | make the value unique on the server. \n If this field is specified 100 | and the generated name exists, the server will NOT return a 409 - 101 | instead, it will either return 201 Created or 500 with Reason ServerTimeout 102 | indicating a unique name could not be found in the time allotted, 103 | and the client should retry (optionally after the time indicated in 104 | the Retry-After header). \n Applied only if Name is not specified. 105 | More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency" 106 | type: string 107 | generation: 108 | description: A sequence number representing a specific generation of 109 | the desired state. Populated by the system. Read-only. 110 | format: int64 111 | type: integer 112 | initializers: 113 | description: "An initializer is a controller which enforces some system 114 | invariant at object creation time. This field is a list of initializers 115 | that have not yet acted on this object. If nil or empty, this object 116 | has been completely initialized. Otherwise, the object is considered 117 | uninitialized and is hidden (in list/watch and get calls) from clients 118 | that haven't explicitly asked to observe uninitialized objects. \n 119 | When an object is created, the system will populate this list with 120 | the current set of initializers. Only privileged users may set or 121 | modify this list. Once it is empty, it may not be modified further 122 | by any user. \n DEPRECATED - initializers are an alpha field and will 123 | be removed in v1.15." 124 | properties: 125 | pending: 126 | description: Pending is a list of initializers that must execute 127 | in order before this object is visible. When the last pending 128 | initializer is removed, and no failing result is set, the initializers 129 | struct will be set to nil and the object is considered as initialized 130 | and visible to all clients. 131 | items: 132 | description: Initializer is information about an initializer that 133 | has not yet completed. 134 | properties: 135 | name: 136 | description: name of the process that is responsible for initializing 137 | this object. 138 | type: string 139 | required: 140 | - name 141 | type: object 142 | type: array 143 | result: 144 | description: If result is set with the Failure field, the object 145 | will be persisted to storage and then deleted, ensuring that other 146 | clients can observe the deletion. 147 | properties: 148 | apiVersion: 149 | description: 'APIVersion defines the versioned schema of this 150 | representation of an object. Servers should convert recognized 151 | schemas to the latest internal value, and may reject unrecognized 152 | values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' 153 | type: string 154 | code: 155 | description: Suggested HTTP return code for this status, 0 if 156 | not set. 157 | format: int32 158 | type: integer 159 | details: 160 | description: Extended data associated with the reason. Each 161 | reason may define its own extended details. This field is 162 | optional and the data returned is not guaranteed to conform 163 | to any schema except that defined by the reason type. 164 | properties: 165 | causes: 166 | description: The Causes array includes more details associated 167 | with the StatusReason failure. Not all StatusReasons may 168 | provide detailed causes. 169 | items: 170 | description: StatusCause provides more information about 171 | an api.Status failure, including cases when multiple 172 | errors are encountered. 173 | properties: 174 | field: 175 | description: "The field of the resource that has caused 176 | this error, as named by its JSON serialization. 177 | May include dot and postfix notation for nested 178 | attributes. Arrays are zero-indexed. Fields may 179 | appear more than once in an array of causes due 180 | to fields having multiple errors. Optional. \n Examples: 181 | \ \"name\" - the field \"name\" on the current 182 | resource \"items[0].name\" - the field \"name\" 183 | on the first array entry in \"items\"" 184 | type: string 185 | message: 186 | description: A human-readable description of the cause 187 | of the error. This field may be presented as-is 188 | to a reader. 189 | type: string 190 | reason: 191 | description: A machine-readable description of the 192 | cause of the error. If this value is empty there 193 | is no information available. 194 | type: string 195 | type: object 196 | type: array 197 | group: 198 | description: The group attribute of the resource associated 199 | with the status StatusReason. 200 | type: string 201 | kind: 202 | description: 'The kind attribute of the resource associated 203 | with the status StatusReason. On some operations may differ 204 | from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' 205 | type: string 206 | name: 207 | description: The name attribute of the resource associated 208 | with the status StatusReason (when there is a single name 209 | which can be described). 210 | type: string 211 | retryAfterSeconds: 212 | description: If specified, the time in seconds before the 213 | operation should be retried. Some errors may indicate 214 | the client must take an alternate action - for those errors 215 | this field may indicate how long to wait before taking 216 | the alternate action. 217 | format: int32 218 | type: integer 219 | uid: 220 | description: 'UID of the resource. (when there is a single 221 | resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids' 222 | type: string 223 | type: object 224 | kind: 225 | description: 'Kind is a string value representing the REST resource 226 | this object represents. Servers may infer this from the endpoint 227 | the client submits requests to. Cannot be updated. In CamelCase. 228 | More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' 229 | type: string 230 | message: 231 | description: A human-readable description of the status of this 232 | operation. 233 | type: string 234 | metadata: 235 | description: 'Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' 236 | properties: 237 | continue: 238 | description: continue may be set if the user set a limit 239 | on the number of items returned, and indicates that the 240 | server has more data available. The value is opaque and 241 | may be used to issue another request to the endpoint that 242 | served this list to retrieve the next set of available 243 | objects. Continuing a consistent list may not be possible 244 | if the server configuration has changed or more than a 245 | few minutes have passed. The resourceVersion field returned 246 | when using this continue value will be identical to the 247 | value in the first response, unless you have received 248 | this token from an error message. 249 | type: string 250 | resourceVersion: 251 | description: 'String that identifies the server''s internal 252 | version of this object that can be used by clients to 253 | determine when objects have changed. Value must be treated 254 | as opaque by clients and passed unmodified back to the 255 | server. Populated by the system. Read-only. More info: 256 | https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' 257 | type: string 258 | selfLink: 259 | description: selfLink is a URL representing this object. 260 | Populated by the system. Read-only. 261 | type: string 262 | type: object 263 | reason: 264 | description: A machine-readable description of why this operation 265 | is in the "Failure" status. If this value is empty there is 266 | no information available. A Reason clarifies an HTTP status 267 | code but does not override it. 268 | type: string 269 | status: 270 | description: 'Status of the operation. One of: "Success" or 271 | "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' 272 | type: string 273 | type: object 274 | required: 275 | - pending 276 | type: object 277 | labels: 278 | additionalProperties: 279 | type: string 280 | description: 'Map of string keys and values that can be used to organize 281 | and categorize (scope and select) objects. May match selectors of 282 | replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels' 283 | type: object 284 | managedFields: 285 | description: "ManagedFields maps workflow-id and version to the set 286 | of fields that are managed by that workflow. This is mostly for internal 287 | housekeeping, and users typically shouldn't need to set or understand 288 | this field. A workflow can be the user's name, a controller's name, 289 | or the name of a specific apply path like \"ci-cd\". The set of fields 290 | is always in the version that the workflow used when modifying the 291 | object. \n This field is alpha and can be changed or removed without 292 | notice." 293 | items: 294 | description: ManagedFieldsEntry is a workflow-id, a FieldSet and the 295 | group version of the resource that the fieldset applies to. 296 | properties: 297 | apiVersion: 298 | description: APIVersion defines the version of this resource that 299 | this field set applies to. The format is "group/version" just 300 | like the top-level APIVersion field. It is necessary to track 301 | the version of a field set because it cannot be automatically 302 | converted. 303 | type: string 304 | fields: 305 | additionalProperties: true 306 | description: Fields identifies a set of fields. 307 | type: object 308 | manager: 309 | description: Manager is an identifier of the workflow managing 310 | these fields. 311 | type: string 312 | operation: 313 | description: Operation is the type of operation which lead to 314 | this ManagedFieldsEntry being created. The only valid values 315 | for this field are 'Apply' and 'Update'. 316 | type: string 317 | time: 318 | description: Time is timestamp of when these fields were set. 319 | It should always be empty if Operation is 'Apply' 320 | format: date-time 321 | type: string 322 | type: object 323 | type: array 324 | name: 325 | description: 'Name must be unique within a namespace. Is required when 326 | creating resources, although some resources may allow a client to 327 | request the generation of an appropriate name automatically. Name 328 | is primarily intended for creation idempotence and configuration definition. 329 | Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' 330 | type: string 331 | namespace: 332 | description: "Namespace defines the space within each name must be unique. 333 | An empty namespace is equivalent to the \"default\" namespace, but 334 | \"default\" is the canonical representation. Not all objects are required 335 | to be scoped to a namespace - the value of this field for those objects 336 | will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: 337 | http://kubernetes.io/docs/user-guide/namespaces" 338 | type: string 339 | ownerReferences: 340 | description: List of objects depended by this object. If ALL objects 341 | in the list have been deleted, this object will be garbage collected. 342 | If this object is managed by a controller, then an entry in this list 343 | will point to this controller, with the controller field set to true. 344 | There cannot be more than one managing controller. 345 | items: 346 | description: OwnerReference contains enough information to let you 347 | identify an owning object. An owning object must be in the same 348 | namespace as the dependent, or be cluster-scoped, so there is no 349 | namespace field. 350 | properties: 351 | apiVersion: 352 | description: API version of the referent. 353 | type: string 354 | blockOwnerDeletion: 355 | description: If true, AND if the owner has the "foregroundDeletion" 356 | finalizer, then the owner cannot be deleted from the key-value 357 | store until this reference is removed. Defaults to false. To 358 | set this field, a user needs "delete" permission of the owner, 359 | otherwise 422 (Unprocessable Entity) will be returned. 360 | type: boolean 361 | controller: 362 | description: If true, this reference points to the managing controller. 363 | type: boolean 364 | kind: 365 | description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' 366 | type: string 367 | name: 368 | description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' 369 | type: string 370 | uid: 371 | description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' 372 | type: string 373 | required: 374 | - apiVersion 375 | - kind 376 | - name 377 | - uid 378 | type: object 379 | type: array 380 | resourceVersion: 381 | description: "An opaque value that represents the internal version of 382 | this object that can be used by clients to determine when objects 383 | have changed. May be used for optimistic concurrency, change detection, 384 | and the watch operation on a resource or set of resources. Clients 385 | must treat these values as opaque and passed unmodified back to the 386 | server. They may only be valid for a particular resource or set of 387 | resources. \n Populated by the system. Read-only. Value must be treated 388 | as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency" 389 | type: string 390 | selfLink: 391 | description: SelfLink is a URL representing this object. Populated by 392 | the system. Read-only. 393 | type: string 394 | uid: 395 | description: "UID is the unique in time and space value for this object. 396 | It is typically generated by the server on successful creation of 397 | a resource and is not allowed to change on PUT operations. \n Populated 398 | by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids" 399 | type: string 400 | type: object 401 | spec: 402 | description: MyKindSpec defines the desired state of MyKind 403 | properties: 404 | deploymentName: 405 | description: DeploymentName is the name of the Deployment resource that 406 | the controller should create. This field must be specified. 407 | maxLength: 64 408 | type: string 409 | replicas: 410 | description: Replicas is the number of replicas that should be specified 411 | on the Deployment resource that the controller creates. If not specified, 412 | one replica will be created. 413 | format: int32 414 | minimum: 0 415 | type: integer 416 | required: 417 | - deploymentName 418 | type: object 419 | status: 420 | description: MyKindStatus defines the observed state of MyKind 421 | properties: 422 | readyReplicas: 423 | description: ReadyReplicas is the number of 'ready' replicas observed 424 | on the Deployment resource created for this MyKind resource. 425 | format: int32 426 | minimum: 0 427 | type: integer 428 | type: object 429 | type: object 430 | versions: 431 | - name: v1beta1 432 | served: true 433 | storage: true 434 | status: 435 | acceptedNames: 436 | kind: "" 437 | plural: "" 438 | conditions: [] 439 | storedVersions: [] 440 | --------------------------------------------------------------------------------