├── ch02 └── README.md ├── ch03 ├── example │ ├── go.mod │ ├── go.sum │ └── main.go ├── go.mod ├── go.sum └── main.go ├── ch04 ├── go.mod ├── go.sum └── main.go ├── ch05 ├── go.mod ├── go.sum └── main.go ├── ch06 ├── clientset │ ├── go.mod │ ├── go.sum │ └── main.go ├── discoveryclient │ ├── go.mod │ ├── go.sum │ └── main.go └── restclient │ ├── go.mod │ ├── go.sum │ └── main.go ├── ch07 ├── discovery.go ├── discovery_test.go ├── go.mod ├── go.sum ├── main.go ├── pod.go ├── pod_test.go ├── rest.go └── rest_test.go ├── ch08 ├── crd-with-cols.yaml ├── crd.yaml ├── go.mod ├── go.sum ├── main.go └── myres.yaml ├── ch09 └── github.com │ └── myid │ └── myresource-crd │ ├── dynamic.go │ ├── dynamic_test.go │ ├── go.mod │ ├── go.sum │ ├── hack │ └── boilerplate.go.txt │ ├── main.go │ ├── pkg │ ├── apis │ │ └── mygroup.example.com │ │ │ └── v1alpha1 │ │ │ ├── doc.go │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ └── zz_generated.deepcopy.go │ └── clientset │ │ └── clientset │ │ ├── clientset.go │ │ ├── doc.go │ │ ├── fake │ │ ├── clientset_generated.go │ │ ├── doc.go │ │ └── register.go │ │ ├── scheme │ │ ├── doc.go │ │ └── register.go │ │ └── typed │ │ └── mygroup.example.com │ │ └── v1alpha1 │ │ ├── doc.go │ │ ├── fake │ │ ├── doc.go │ │ ├── fake_mygroup.example.com_client.go │ │ └── fake_myresource.go │ │ ├── generated_expansion.go │ │ ├── mygroup.example.com_client.go │ │ └── myresource.go │ └── unstructured.go ├── ch10 ├── client │ ├── go.mod │ ├── go.sum │ └── main.go ├── events │ ├── go.mod │ ├── go.sum │ └── main.go ├── ex1 │ ├── go.mod │ ├── go.sum │ └── main.go ├── ex2 │ ├── go.mod │ ├── go.sum │ └── main.go ├── injectors │ ├── go.mod │ ├── go.sum │ └── main.go └── logging │ ├── go.mod │ ├── go.sum │ └── main.go ├── ch11 ├── go.mod ├── go.sum ├── main.go └── reconciler.go ├── ch12 ├── crd │ └── crd-with-cols.yaml ├── go.mod ├── go.sum ├── main.go ├── reconcile_test.go ├── reconciler.go └── suite_test.go └── ch13 └── myresource-kb ├── .dockerignore ├── .gitignore ├── Dockerfile ├── Makefile ├── PROJECT ├── README.md ├── api ├── v1alpha1 │ ├── groupversion_info.go │ ├── hub.go │ ├── myresource_types.go │ └── zz_generated.deepcopy.go └── v1beta1 │ ├── groupversion_info.go │ ├── myresource_conversion.go │ ├── myresource_types.go │ ├── myresource_webhook.go │ └── zz_generated.deepcopy.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ └── mygroup.myid.dev_myresources.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_myresources.yaml │ │ └── webhook_in_myresources.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_config_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── myresource_editor_role.yaml │ ├── myresource_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── mygroup_v1alpha1_myresource.yaml │ └── mygroup_v1beta1_myresource.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── service.yaml ├── controllers ├── deployment.go ├── myresource_controller.go ├── reconcile_test.go ├── status.go └── suite_test.go ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt └── main.go /ch03/example/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch3/example 2 | 3 | go 1.19 4 | 5 | require ( 6 | k8s.io/api v0.25.3 7 | k8s.io/apimachinery v0.25.3 8 | ) 9 | 10 | require ( 11 | github.com/go-logr/logr v1.2.3 // indirect 12 | github.com/gogo/protobuf v1.3.2 // indirect 13 | github.com/google/gofuzz v1.1.0 // indirect 14 | github.com/json-iterator/go v1.1.12 // indirect 15 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 16 | github.com/modern-go/reflect2 v1.0.2 // indirect 17 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 18 | golang.org/x/text v0.3.7 // indirect 19 | gopkg.in/inf.v0 v0.9.1 // indirect 20 | gopkg.in/yaml.v2 v2.4.0 // indirect 21 | k8s.io/klog/v2 v2.70.1 // indirect 22 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 23 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 24 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 25 | sigs.k8s.io/yaml v1.2.0 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /ch03/example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | 10 | corev1 "k8s.io/api/core/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/apimachinery/pkg/runtime/schema" 14 | kjson "k8s.io/apimachinery/pkg/runtime/serializer/json" 15 | ) 16 | 17 | func main() { 18 | err := createPod() 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | 24 | func createPod() error { 25 | pod := createPodObject() // ➊ 26 | 27 | serializer := getJSONSerializer() 28 | postBody, err := serializePodObject(serializer, pod) // ➋ 29 | if err != nil { 30 | return err 31 | } 32 | 33 | reqCreate, err := buildPostRequest(postBody) // ➌ 34 | if err != nil { 35 | return err 36 | } 37 | 38 | client := &http.Client{} 39 | resp, err := client.Do(reqCreate) // ➍ 40 | if err != nil { 41 | return err 42 | } 43 | defer resp.Body.Close() 44 | 45 | body, err := io.ReadAll(resp.Body) // ➎ 46 | if err != nil { 47 | return err 48 | } 49 | 50 | if resp.StatusCode < 300 { // ➏ 51 | createdPod, err := deserializePodBody(serializer, body) // ➐ 52 | if err != nil { 53 | return err 54 | } 55 | json, err := json.MarshalIndent(createdPod, "", " ") 56 | if err != nil { 57 | return err 58 | } 59 | fmt.Printf("%s\n", json) // ➑ 60 | } else { 61 | status, err := deserializeStatusBody(serializer, body) // ➒ 62 | if err != nil { 63 | return err 64 | } 65 | json, err := json.MarshalIndent(status, "", " ") 66 | if err != nil { 67 | return err 68 | } 69 | fmt.Printf("%s\n", json) // ➓ 70 | } 71 | return nil 72 | } 73 | 74 | func createPodObject() *corev1.Pod { // ➊ 75 | pod := corev1.Pod{ 76 | Spec: corev1.PodSpec{ 77 | Containers: []corev1.Container{ 78 | { 79 | Name: "runtime", 80 | Image: "nginx", 81 | }, 82 | }, 83 | }, 84 | } 85 | 86 | pod.SetName("my-pod") 87 | pod.SetLabels(map[string]string{ 88 | "app.kubernetes.io/component": "my-component", 89 | "app.kubernetes.io/name": "a-name", 90 | }) 91 | return &pod 92 | } 93 | 94 | func serializePodObject( // ➋ 95 | serializer runtime.Serializer, 96 | pod *corev1.Pod, 97 | ) ( 98 | io.Reader, 99 | error, 100 | ) { 101 | var buf bytes.Buffer 102 | err := serializer.Encode(pod, &buf) 103 | if err != nil { 104 | return nil, err 105 | } 106 | return &buf, nil 107 | } 108 | 109 | func buildPostRequest( // ➌ 110 | body io.Reader, 111 | ) ( 112 | *http.Request, 113 | error, 114 | ) { 115 | reqCreate, err := http.NewRequest( 116 | "POST", 117 | "http://127.0.0.1:8001/api/v1/namespaces/default/pods", 118 | body, 119 | ) 120 | if err != nil { 121 | return nil, err 122 | } 123 | reqCreate.Header.Add( 124 | "Accept", 125 | "application/json", 126 | ) 127 | reqCreate.Header.Add( 128 | "Content-Type", 129 | "application/json", 130 | ) 131 | return reqCreate, nil 132 | } 133 | 134 | func deserializePodBody( // ➐ 135 | serializer runtime.Serializer, 136 | body []byte, 137 | ) ( 138 | *corev1.Pod, 139 | error, 140 | ) { 141 | var result corev1.Pod 142 | _, _, err := serializer.Decode(body, nil, &result) 143 | if err != nil { 144 | return nil, err 145 | } 146 | return &result, nil 147 | 148 | } 149 | 150 | func deserializeStatusBody( // ➒ 151 | serializer runtime.Serializer, 152 | body []byte, 153 | ) ( 154 | *metav1.Status, 155 | error, 156 | ) { 157 | var status metav1.Status 158 | _, _, err := serializer.Decode(body, nil, &status) 159 | if err != nil { 160 | return nil, err 161 | } 162 | return &status, nil 163 | } 164 | 165 | func getJSONSerializer() runtime.Serializer { 166 | scheme := runtime.NewScheme() 167 | scheme.AddKnownTypes( 168 | schema.GroupVersion{ 169 | Group: "", 170 | Version: "v1", 171 | }, 172 | &corev1.Pod{}, 173 | &metav1.Status{}, 174 | ) 175 | return kjson.NewSerializerWithOptions( 176 | kjson.SimpleMetaFactory{}, 177 | nil, 178 | scheme, 179 | kjson.SerializerOptions{}, 180 | ) 181 | } 182 | -------------------------------------------------------------------------------- /ch03/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch3 2 | 3 | go 1.19 4 | 5 | require ( 6 | k8s.io/api v0.25.3 7 | k8s.io/apimachinery v0.25.3 8 | k8s.io/client-go v0.25.3 9 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed 10 | ) 11 | 12 | require ( 13 | github.com/PuerkitoBio/purell v1.1.1 // indirect 14 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 17 | github.com/go-logr/logr v1.2.3 // indirect 18 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 19 | github.com/go-openapi/jsonreference v0.19.5 // indirect 20 | github.com/go-openapi/swag v0.19.14 // indirect 21 | github.com/gogo/protobuf v1.3.2 // indirect 22 | github.com/golang/protobuf v1.5.2 // indirect 23 | github.com/google/gnostic v0.5.7-v3refs // indirect 24 | github.com/google/gofuzz v1.1.0 // indirect 25 | github.com/imdario/mergo v0.3.6 // indirect 26 | github.com/josharian/intern v1.0.0 // indirect 27 | github.com/json-iterator/go v1.1.12 // indirect 28 | github.com/mailru/easyjson v0.7.6 // indirect 29 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 30 | github.com/modern-go/reflect2 v1.0.2 // indirect 31 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 32 | github.com/spf13/pflag v1.0.5 // indirect 33 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 34 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 35 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 36 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 37 | golang.org/x/text v0.3.7 // indirect 38 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 39 | google.golang.org/appengine v1.6.7 // indirect 40 | google.golang.org/protobuf v1.28.0 // indirect 41 | gopkg.in/inf.v0 v0.9.1 // indirect 42 | gopkg.in/yaml.v2 v2.4.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | k8s.io/klog/v2 v2.70.1 // indirect 45 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 46 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 47 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 48 | sigs.k8s.io/yaml v1.2.0 // indirect 49 | ) 50 | -------------------------------------------------------------------------------- /ch03/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | appsv1 "k8s.io/api/apps/v1" 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/api/resource" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/labels" 11 | "k8s.io/utils/pointer" 12 | 13 | "k8s.io/client-go/kubernetes" 14 | "k8s.io/client-go/tools/clientcmd" 15 | ) 16 | 17 | func main() { 18 | // # Specific content in core/v1 19 | // ## ResourceList 20 | requests := corev1.ResourceList{ 21 | corev1.ResourceMemory: *resource.NewQuantity(64*1024*1024, resource.BinarySI), 22 | corev1.ResourceCPU: *resource.NewMilliQuantity(250, resource.DecimalSI), 23 | } 24 | _ = requests 25 | 26 | // # Writing Kubernetes Resources in Go 27 | // ## Importing the package 28 | myDep := appsv1.Deployment{} 29 | _ = myDep 30 | 31 | // ## The ObjectMeta fields 32 | // ### Name 33 | configmap := corev1.ConfigMap{} 34 | configmap.SetName("config") 35 | 36 | // ### Labels and annotations 37 | mylabels := map[string]string{ 38 | "app.kubernetes.io/component": "my-component", 39 | "app.kubernetes.io/name": "a-name", 40 | } 41 | 42 | mylabels["app.kubernetes.io/part-of"] = "my-app" 43 | 44 | mySet := labels.Set{ 45 | "app.kubernetes.io/component": "my-component", 46 | "app.kubernetes.io/name": "a-name", 47 | } 48 | mySet["app.kubernetes.io/part-of"] = "my-app" 49 | 50 | // ### OwnerReferences 51 | // Get the object to reference 52 | clientset, err := getClientset() 53 | if err != nil { 54 | panic(err) 55 | } 56 | pod, err := clientset.CoreV1().Pods("myns"). 57 | Get(context.TODO(), "mypodname", metav1.GetOptions{}) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | // Solution 1: set the APIVersion and Kind of the Pod 63 | // then copy all information from the pod 64 | 65 | pod.SetGroupVersionKind( 66 | corev1.SchemeGroupVersion.WithKind("Pod"), 67 | ) 68 | ownerRef := metav1.OwnerReference{ 69 | APIVersion: pod.APIVersion, 70 | Kind: pod.Kind, 71 | Name: pod.GetName(), 72 | UID: pod.GetUID(), 73 | } 74 | 75 | // Solution 2: Copy name and uid from pod 76 | // then set APIVersion and Kind on the OwnerReference 77 | 78 | ownerRef = metav1.OwnerReference{ 79 | Name: pod.GetName(), 80 | UID: pod.GetUID(), 81 | } 82 | ownerRef.APIVersion, ownerRef.Kind = 83 | corev1.SchemeGroupVersion.WithKind("Pod"). 84 | ToAPIVersionAndKind() 85 | 86 | // #### Setting Controller 87 | // Solution 1: declare a value and use its address 88 | controller := true 89 | ownerRef.Controller = &controller 90 | 91 | // Solution 2: use the BoolPtr function 92 | ownerRef.Controller = pointer.BoolPtr(true) 93 | 94 | // ## Comparison with writing YAML manifests 95 | // Solution 1 96 | pod1 := corev1.Pod{ 97 | Spec: corev1.PodSpec{ 98 | Containers: []corev1.Container{ 99 | { 100 | Name: "runtime", 101 | Image: "nginx", 102 | }, 103 | }, 104 | }, 105 | } 106 | pod1.SetName("my-pod") 107 | pod1.SetLabels(map[string]string{ 108 | "component": "my-component", 109 | }) 110 | 111 | // Solution 2 112 | pod2 := corev1.Pod{ 113 | ObjectMeta: metav1.ObjectMeta{ 114 | Name: "nginx", 115 | Labels: map[string]string{ 116 | "component": "mycomponent", 117 | }, 118 | }, 119 | Spec: corev1.PodSpec{ 120 | Containers: []corev1.Container{ 121 | { 122 | Name: "runtime", 123 | Image: "nginx", 124 | }, 125 | }, 126 | }, 127 | } 128 | _ = pod2 129 | } 130 | 131 | func getClientset() (*kubernetes.Clientset, error) { 132 | config, err := 133 | clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 134 | clientcmd.NewDefaultClientConfigLoadingRules(), 135 | nil, 136 | ).ClientConfig() 137 | if err != nil { 138 | return nil, err 139 | } 140 | return kubernetes.NewForConfig(config) 141 | } 142 | -------------------------------------------------------------------------------- /ch04/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch4 2 | 3 | go 1.19 4 | 5 | require ( 6 | gopkg.in/inf.v0 v0.9.1 7 | k8s.io/api v0.25.3 8 | k8s.io/apimachinery v0.25.3 9 | k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 10 | ) 11 | 12 | require ( 13 | github.com/go-logr/logr v1.2.3 // indirect 14 | github.com/gogo/protobuf v1.3.2 // indirect 15 | github.com/google/gofuzz v1.1.0 // indirect 16 | github.com/json-iterator/go v1.1.12 // indirect 17 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 18 | github.com/modern-go/reflect2 v1.0.2 // indirect 19 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 20 | golang.org/x/text v0.3.7 // indirect 21 | gopkg.in/yaml.v2 v2.4.0 // indirect 22 | k8s.io/klog/v2 v2.80.1 // indirect 23 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 24 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /ch04/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gopkg.in/inf.v0" 7 | appsv1 "k8s.io/api/apps/v1" 8 | "k8s.io/apimachinery/pkg/api/resource" 9 | "k8s.io/apimachinery/pkg/util/intstr" 10 | "k8s.io/utils/pointer" 11 | ) 12 | 13 | func main() { 14 | // # Pointers 15 | // ## Getting the reference of a value 16 | spec := appsv1.DeploymentSpec{ 17 | Replicas: pointer.Int32(3), 18 | } 19 | 20 | // ## Dereferencing a pointer 21 | replicas := pointer.Int32Deref(spec.Replicas, 1) 22 | _ = replicas 23 | 24 | // ## Comparing two referenced values 25 | spec1 := appsv1.DeploymentSpec{ 26 | Replicas: pointer.Int32(3), 27 | } 28 | 29 | spec2 := appsv1.DeploymentSpec{ 30 | Replicas: pointer.Int32(1), 31 | } 32 | 33 | eq := pointer.Int32Equal( 34 | spec1.Replicas, 35 | spec2.Replicas, 36 | ) 37 | _ = eq 38 | 39 | // # Quantities 40 | // ## Parsing a string as Quantity 41 | q1 := resource.MustParse("1Mi") 42 | q1, err := resource.ParseQuantity("1Mi") 43 | _ = q1 44 | _ = err 45 | 46 | // ## Using an inf.Dec as Quantity 47 | newDec := inf.NewDec(4, 3) 48 | fmt.Printf("newDec: %s\n", newDec) // 0.004 49 | q2 := resource.NewDecimalQuantity(*newDec, resource.DecimalExponent) 50 | fmt.Printf("q2: %s\n", q2) // 4e-3 51 | 52 | // ## Using a scaled integer as Quantity 53 | q3 := resource.NewScaledQuantity(4, 3) 54 | fmt.Printf("q3: %s\n", q3) // 4k 55 | 56 | q3.SetScaled(5, 6) 57 | fmt.Printf("q3: %s\n", q3) // 5M 58 | 59 | fmt.Printf("q3 scaled to 3: %d\n", q3.ScaledValue(3)) // 5.000 60 | fmt.Printf("q3 scaled to 0: %d\n", q3.ScaledValue(0)) // 5.000.000 61 | 62 | q4 := resource.NewQuantity(4000, resource.DecimalSI) 63 | fmt.Printf("q4: %s\n", q4) // 4k 64 | 65 | q5 := resource.NewQuantity(1024, resource.BinarySI) 66 | fmt.Printf("q5: %s\n", q5) // 1Ki 67 | 68 | q6 := resource.NewQuantity(4000, resource.DecimalExponent) 69 | fmt.Printf("q6: %s\n", q6) // 4e3 70 | 71 | q7 := resource.NewMilliQuantity(5, resource.DecimalSI) 72 | fmt.Printf("q7: %s\n", q7) // 5m 73 | 74 | q8 := resource.NewMilliQuantity(5, resource.DecimalExponent) 75 | fmt.Printf("q8: %s\n", q8) // 5e-3 76 | 77 | q8.SetMilli(6) 78 | fmt.Printf("q8: %s\n", q8) // 6e-3 79 | 80 | fmt.Printf("milli value of q8: %d\n", q8.MilliValue()) // 6 81 | 82 | // ## Operations on Quantities 83 | q9 := resource.MustParse("4M") 84 | q10 := resource.MustParse("3M") 85 | 86 | q9.Add(q10) 87 | fmt.Printf("4M + 3M: %s\n", q9.String()) // 7M 88 | 89 | q9.Sub(q10) 90 | fmt.Printf("7M - 3M: %s\n", q9.String()) // 4M 91 | 92 | cmp := q9.Cmp(q10) 93 | fmt.Printf("4M >? 3M: %d\n", cmp) // 1 94 | 95 | cmp = q9.CmpInt64(4_000_000) 96 | fmt.Printf("4M >? 4.000.000: %d\n", cmp) // 0 97 | 98 | q9.Neg() 99 | fmt.Printf("negative of 4M: %s\n", q9.String()) // -4M 100 | 101 | eq = q9.Equal(q10) 102 | fmt.Printf("4M ==? 3M: %v\n", eq) // false 103 | 104 | // # IntOrString 105 | ios1 := intstr.FromInt(10) 106 | fmt.Printf("ios1: %s\n", ios1.String()) // 10 107 | 108 | ios2 := intstr.FromString("value") 109 | fmt.Printf("ios2: %s\n", ios2.String()) // value 110 | 111 | ios3 := intstr.Parse("100") 112 | fmt.Printf("ios3 as string: %s\n", ios3.String()) // 100 113 | fmt.Printf("ios3 as int: %d\n", ios3.IntValue()) // 100 114 | 115 | ios4 := intstr.Parse("value") 116 | fmt.Printf("ios4 as string: %s\n", ios4.String()) // value 117 | fmt.Printf("ios4 as int: %d\n", ios4.IntValue()) // 0 118 | 119 | fmt.Printf("ios4 or 'default': %s\n", intstr.ValueOrDefault(&ios4, intstr.Parse("default"))) // value 120 | fmt.Printf("nil or 'default': %s\n", intstr.ValueOrDefault(nil, intstr.Parse("default"))) // default 121 | 122 | ios5 := intstr.Parse("10%") 123 | scaled, _ := intstr.GetScaledValueFromIntOrPercent(&ios5, 5000, true) 124 | fmt.Printf("10%% of 5000: %d\n", scaled) // 500 125 | } 126 | -------------------------------------------------------------------------------- /ch05/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch5 2 | 3 | go 1.19 4 | 5 | require ( 6 | k8s.io/api v0.25.3 7 | k8s.io/apimachinery v0.25.3 8 | ) 9 | 10 | require ( 11 | github.com/go-logr/logr v1.2.3 // indirect 12 | github.com/gogo/protobuf v1.3.2 // indirect 13 | github.com/google/gofuzz v1.1.0 // indirect 14 | github.com/json-iterator/go v1.1.12 // indirect 15 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 16 | github.com/modern-go/reflect2 v1.0.2 // indirect 17 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 18 | golang.org/x/text v0.3.7 // indirect 19 | gopkg.in/inf.v0 v0.9.1 // indirect 20 | gopkg.in/yaml.v2 v2.4.0 // indirect 21 | k8s.io/klog/v2 v2.70.1 // indirect 22 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 23 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 24 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /ch06/clientset/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch6/clientset 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/spf13/pflag v1.0.5 7 | k8s.io/api v0.25.0 8 | k8s.io/apimachinery v0.25.0 9 | k8s.io/client-go v0.25.0 10 | k8s.io/klog/v2 v2.70.1 11 | sigs.k8s.io/controller-runtime v0.13.0 12 | ) 13 | 14 | require ( 15 | github.com/PuerkitoBio/purell v1.1.1 // indirect 16 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 19 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 20 | github.com/go-logr/logr v1.2.3 // indirect 21 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 22 | github.com/go-openapi/jsonreference v0.19.5 // indirect 23 | github.com/go-openapi/swag v0.19.14 // indirect 24 | github.com/gogo/protobuf v1.3.2 // indirect 25 | github.com/golang/protobuf v1.5.2 // indirect 26 | github.com/google/gnostic v0.5.7-v3refs // indirect 27 | github.com/google/gofuzz v1.1.0 // indirect 28 | github.com/imdario/mergo v0.3.12 // indirect 29 | github.com/josharian/intern v1.0.0 // indirect 30 | github.com/json-iterator/go v1.1.12 // indirect 31 | github.com/mailru/easyjson v0.7.6 // indirect 32 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 33 | github.com/modern-go/reflect2 v1.0.2 // indirect 34 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 35 | github.com/pkg/errors v0.9.1 // indirect 36 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 37 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 38 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 39 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 40 | golang.org/x/text v0.3.7 // indirect 41 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 42 | google.golang.org/appengine v1.6.7 // indirect 43 | google.golang.org/protobuf v1.28.0 // indirect 44 | gopkg.in/inf.v0 v0.9.1 // indirect 45 | gopkg.in/yaml.v2 v2.4.0 // indirect 46 | gopkg.in/yaml.v3 v3.0.1 // indirect 47 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 48 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 49 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 50 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 51 | sigs.k8s.io/yaml v1.3.0 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /ch06/discoveryclient/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch6/discoveryclient 2 | 3 | go 1.19 4 | 5 | require k8s.io/client-go v0.25.3 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/go-logr/logr v1.2.3 // indirect 10 | github.com/gogo/protobuf v1.3.2 // indirect 11 | github.com/golang/protobuf v1.5.2 // indirect 12 | github.com/google/gofuzz v1.1.0 // indirect 13 | github.com/imdario/mergo v0.3.6 // indirect 14 | github.com/json-iterator/go v1.1.12 // indirect 15 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 16 | github.com/modern-go/reflect2 v1.0.2 // indirect 17 | github.com/spf13/pflag v1.0.5 // indirect 18 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 19 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 20 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 21 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 22 | golang.org/x/text v0.3.7 // indirect 23 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 24 | google.golang.org/appengine v1.6.7 // indirect 25 | google.golang.org/protobuf v1.28.0 // indirect 26 | gopkg.in/inf.v0 v0.9.1 // indirect 27 | gopkg.in/yaml.v2 v2.4.0 // indirect 28 | k8s.io/apimachinery v0.25.3 // indirect 29 | k8s.io/klog/v2 v2.70.1 // indirect 30 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 31 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 32 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 33 | sigs.k8s.io/yaml v1.2.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /ch06/discoveryclient/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | "k8s.io/client-go/discovery" 8 | "k8s.io/client-go/discovery/cached/memory" 9 | "k8s.io/client-go/rest" 10 | "k8s.io/client-go/restmapper" 11 | "k8s.io/client-go/tools/clientcmd" 12 | ) 13 | 14 | func main() { 15 | config, err := getConfig() 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | client, err := discovery.NewDiscoveryClientForConfig(config) 21 | 22 | restMapper := 23 | restmapper.NewDeferredDiscoveryRESTMapper( 24 | memory.NewMemCacheClient(client), 25 | ) 26 | mapping, _ := restMapper.RESTMapping( 27 | schema.GroupKind{Group: "apps", Kind: "Deployment"}, 28 | ) 29 | fmt.Printf("single mapping: %+v\n", *mapping) 30 | // {Resource:apps/v1, Resource=deployments GroupVersionKind:apps/v1, Kind=Deployment Scope:0x1e68d00} 31 | 32 | mappings, _ := restMapper.RESTMappings( 33 | schema.GroupKind{Group: "apps", Kind: "Deployment"}, 34 | ) 35 | for _, mapping := range mappings { 36 | fmt.Printf("mapping: %+v\n", *mapping) 37 | } 38 | // {Resource:apps/v1, Resource=deployments GroupVersionKind:apps/v1, Kind=Deployment Scope:0x1e68d00} 39 | 40 | kinds, _ := restMapper.KindsFor( 41 | schema.GroupVersionResource{Group: "", Version: "", Resource: "deployment"}, 42 | ) 43 | fmt.Printf("kinds: %+v\n", kinds) 44 | // [apps/v1, Kind=Deployment] 45 | 46 | resources, _ := restMapper.ResourcesFor( 47 | schema.GroupVersionResource{Group: "", Version: "", Resource: "deployment"}, 48 | ) 49 | fmt.Printf("resources: %+v\n", resources) 50 | // [apps/v1, Resource=deployments] 51 | } 52 | 53 | func getConfig() (*rest.Config, error) { 54 | return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 55 | clientcmd.NewDefaultClientConfigLoadingRules(), 56 | nil, 57 | ).ClientConfig() 58 | } 59 | -------------------------------------------------------------------------------- /ch06/restclient/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch6/restclient 2 | 3 | go 1.19 4 | 5 | require k8s.io/client-go v0.25.3 6 | 7 | require ( 8 | github.com/PuerkitoBio/purell v1.1.1 // indirect 9 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 10 | github.com/davecgh/go-spew v1.1.1 // indirect 11 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 12 | github.com/go-logr/logr v1.2.3 // indirect 13 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 14 | github.com/go-openapi/jsonreference v0.19.5 // indirect 15 | github.com/go-openapi/swag v0.19.14 // indirect 16 | github.com/gogo/protobuf v1.3.2 // indirect 17 | github.com/golang/protobuf v1.5.2 // indirect 18 | github.com/google/gnostic v0.5.7-v3refs // indirect 19 | github.com/google/gofuzz v1.1.0 // indirect 20 | github.com/imdario/mergo v0.3.6 // indirect 21 | github.com/josharian/intern v1.0.0 // indirect 22 | github.com/json-iterator/go v1.1.12 // indirect 23 | github.com/mailru/easyjson v0.7.6 // indirect 24 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 25 | github.com/modern-go/reflect2 v1.0.2 // indirect 26 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 27 | github.com/spf13/pflag v1.0.5 // indirect 28 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 29 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 30 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 31 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 32 | golang.org/x/text v0.3.7 // indirect 33 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 34 | google.golang.org/appengine v1.6.7 // indirect 35 | google.golang.org/protobuf v1.28.0 // indirect 36 | gopkg.in/inf.v0 v0.9.1 // indirect 37 | gopkg.in/yaml.v2 v2.4.0 // indirect 38 | gopkg.in/yaml.v3 v3.0.1 // indirect 39 | k8s.io/api v0.25.3 // indirect 40 | k8s.io/apimachinery v0.25.3 // indirect 41 | k8s.io/klog/v2 v2.70.1 // indirect 42 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 43 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 44 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 45 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 46 | sigs.k8s.io/yaml v1.2.0 // indirect 47 | ) 48 | -------------------------------------------------------------------------------- /ch06/restclient/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/client-go/kubernetes" 9 | "k8s.io/client-go/rest" 10 | "k8s.io/client-go/tools/clientcmd" 11 | ) 12 | 13 | func main() { 14 | ctx := context.Background() 15 | config, err := getConfig() 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | clientset, err := kubernetes.NewForConfig(config) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | // # Getting result as table 26 | restClient := clientset.CoreV1().RESTClient() // ➊ 27 | req := restClient.Get(). 28 | Namespace("project1"). // ➋ 29 | Resource("pods"). // ➌ 30 | SetHeader( // ➍ 31 | "Accept", 32 | fmt.Sprintf( 33 | "application/json;as=Table;v=%s;g=%s", 34 | metav1.SchemeGroupVersion.Version, 35 | metav1.GroupName, 36 | )) 37 | 38 | var result metav1.Table // ➎ 39 | err = req.Do(ctx). // ➏ 40 | Into(&result) // ➐ 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | for _, colDef := range result.ColumnDefinitions { // ➑ 46 | // display header 47 | fmt.Printf("%v\t", colDef.Name) 48 | } 49 | fmt.Printf("\n") 50 | 51 | for _, row := range result.Rows { // ➒ 52 | for _, cell := range row.Cells { // ➓ 53 | // display cell 54 | fmt.Printf("%v\t", cell) 55 | } 56 | fmt.Printf("\n") 57 | } 58 | } 59 | 60 | func getConfig() (*rest.Config, error) { 61 | return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 62 | clientcmd.NewDefaultClientConfigLoadingRules(), 63 | nil, 64 | ).ClientConfig() 65 | } 66 | -------------------------------------------------------------------------------- /ch07/discovery.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | 6 | "k8s.io/client-go/kubernetes" 7 | ) 8 | 9 | func checkMinimalServerVersion( 10 | clientset kubernetes.Interface, 11 | minMinor int, 12 | ) (bool, error) { 13 | discoveryClient := clientset.Discovery() 14 | info, err := discoveryClient.ServerVersion() 15 | if err != nil { 16 | return false, err 17 | } 18 | major, err := strconv.Atoi(info.Major) 19 | if err != nil { 20 | return false, err 21 | } 22 | minor, err := strconv.Atoi(info.Minor) 23 | if err != nil { 24 | return false, err 25 | } 26 | 27 | return major == 1 && minor >= minMinor, nil 28 | } 29 | -------------------------------------------------------------------------------- /ch07/discovery_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "k8s.io/apimachinery/pkg/version" 7 | fakediscovery "k8s.io/client-go/discovery/fake" 8 | "k8s.io/client-go/kubernetes/fake" 9 | ) 10 | 11 | func Test_getServerVersion(t *testing.T) { 12 | type server struct { 13 | major string 14 | minor string 15 | } 16 | tests := []struct { 17 | name string 18 | server server 19 | min int 20 | want bool 21 | err bool 22 | }{ 23 | { 24 | name: "minimal not respected", 25 | server: server{ 26 | major: "1", 27 | minor: "9", 28 | }, 29 | min: 10, 30 | want: false, 31 | err: false, 32 | }, 33 | { 34 | name: "minimal respected", 35 | server: server{ 36 | major: "1", 37 | minor: "11", 38 | }, 39 | min: 10, 40 | want: true, 41 | err: false, 42 | }, 43 | { 44 | name: "version of server is unreadable", 45 | server: server{ 46 | major: "aze", 47 | minor: "11", 48 | }, 49 | err: true, 50 | }, 51 | } 52 | 53 | for _, tt := range tests { 54 | client := fake.NewSimpleClientset() 55 | 56 | fakeDiscovery, ok := client.Discovery().(*fakediscovery.FakeDiscovery) 57 | if !ok { 58 | t.Fatalf("couldn't convert Discovery() to *FakeDiscovery") 59 | } 60 | 61 | fakeDiscovery.FakedServerVersion = &version.Info{ 62 | Major: tt.server.major, 63 | Minor: tt.server.minor, 64 | } 65 | 66 | res, err := checkMinimalServerVersion(client, 10) 67 | if err != nil != tt.err { 68 | t.Errorf("Expected error: %v\n", tt.err) 69 | } 70 | if res != tt.want { 71 | t.Errorf("Expected %v, got %v\n", tt.want, res) 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /ch07/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch7 2 | 3 | go 1.19 4 | 5 | require ( 6 | k8s.io/api v0.25.3 7 | k8s.io/apimachinery v0.25.3 8 | k8s.io/client-go v0.25.3 9 | ) 10 | 11 | require ( 12 | github.com/PuerkitoBio/purell v1.1.1 // indirect 13 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 16 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 17 | github.com/go-logr/logr v1.2.3 // indirect 18 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 19 | github.com/go-openapi/jsonreference v0.19.5 // indirect 20 | github.com/go-openapi/swag v0.19.14 // indirect 21 | github.com/gogo/protobuf v1.3.2 // indirect 22 | github.com/golang/protobuf v1.5.2 // indirect 23 | github.com/google/gnostic v0.5.7-v3refs // indirect 24 | github.com/google/gofuzz v1.1.0 // indirect 25 | github.com/josharian/intern v1.0.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/mailru/easyjson v0.7.6 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 31 | github.com/pkg/errors v0.9.1 // indirect 32 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 33 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 34 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 35 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 36 | golang.org/x/text v0.3.7 // indirect 37 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 38 | google.golang.org/appengine v1.6.7 // indirect 39 | google.golang.org/protobuf v1.28.0 // indirect 40 | gopkg.in/inf.v0 v0.9.1 // indirect 41 | gopkg.in/yaml.v2 v2.4.0 // indirect 42 | gopkg.in/yaml.v3 v3.0.1 // indirect 43 | k8s.io/klog/v2 v2.70.1 // indirect 44 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 45 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 46 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 47 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 48 | sigs.k8s.io/yaml v1.2.0 // indirect 49 | ) 50 | -------------------------------------------------------------------------------- /ch07/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /ch07/pod.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/client-go/kubernetes" 9 | ) 10 | 11 | func CreatePod( 12 | ctx context.Context, 13 | clientset kubernetes.Interface, 14 | name string, 15 | namespace string, 16 | image string, 17 | ) (pod *corev1.Pod, err error) { 18 | 19 | podToCreate := corev1.Pod{ 20 | Spec: corev1.PodSpec{ 21 | Containers: []corev1.Container{ 22 | { 23 | Name: "runtime", 24 | Image: image, 25 | }, 26 | }, 27 | }, 28 | } 29 | podToCreate.SetName(name) 30 | 31 | return clientset.CoreV1(). 32 | Pods(namespace). 33 | Create( 34 | ctx, 35 | &podToCreate, 36 | metav1.CreateOptions{}, 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /ch07/pod_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | "k8s.io/client-go/kubernetes/fake" 12 | ktesting "k8s.io/client-go/testing" 13 | ) 14 | 15 | // TestCreatePod: Checking the result of the function 16 | func TestCreatePod1(t *testing.T) { 17 | var ( 18 | name = "a-name" 19 | namespace = "a-namespace" 20 | image = "an-image" 21 | 22 | wantPod = &corev1.Pod{ 23 | ObjectMeta: metav1.ObjectMeta{ 24 | Name: "a-name", 25 | Namespace: "a-namespace", 26 | }, 27 | Spec: corev1.PodSpec{ 28 | Containers: []corev1.Container{ 29 | { 30 | Name: "runtime", 31 | Image: "an-image", 32 | }, 33 | }, 34 | }, 35 | } 36 | ) 37 | 38 | clientset := fake.NewSimpleClientset() 39 | gotPod, err := CreatePod( 40 | context.Background(), 41 | clientset, 42 | name, 43 | namespace, 44 | image, 45 | ) 46 | 47 | if err != nil { 48 | t.Errorf("err = %v, want nil", err) 49 | } 50 | if !reflect.DeepEqual(gotPod, wantPod) { 51 | t.Errorf("CreatePod() = %v, want %v", 52 | gotPod, 53 | wantPod, 54 | ) 55 | } 56 | } 57 | 58 | // TestCreatePod2: Reacting to Actions 59 | func TestCreatePod2(t *testing.T) { 60 | var ( 61 | name = "a-name" 62 | namespace = "a-namespace" 63 | image = "an-image" 64 | 65 | wantPod = &corev1.Pod{ 66 | ObjectMeta: metav1.ObjectMeta{ 67 | Name: "a-name", 68 | Namespace: "a-namespace", 69 | }, 70 | Spec: corev1.PodSpec{ 71 | NodeName: "node1", 72 | Containers: []corev1.Container{ 73 | { 74 | Name: "runtime", 75 | Image: "an-image", 76 | }, 77 | }, 78 | }, 79 | } 80 | ) 81 | 82 | clientset := fake.NewSimpleClientset() 83 | 84 | clientset.Fake.PrependReactor("create", "pods", func( 85 | action ktesting.Action, 86 | ) (handled bool, ret runtime.Object, err error) { 87 | act := action.(ktesting.CreateAction) 88 | ret = act.GetObject() 89 | pod := ret.(*corev1.Pod) 90 | pod.Spec.NodeName = "node1" 91 | return false, pod, nil 92 | }) 93 | 94 | gotPod, err := CreatePod( 95 | context.Background(), 96 | clientset, 97 | name, 98 | namespace, 99 | image, 100 | ) 101 | 102 | if err != nil { 103 | t.Errorf("err = %v, want nil", err) 104 | } 105 | if !reflect.DeepEqual(gotPod, wantPod) { 106 | t.Errorf("CreatePod() = %v, want %v", 107 | gotPod, 108 | wantPod, 109 | ) 110 | } 111 | } 112 | 113 | // TestCreatePod3: Checking the actions 114 | func TestCreatePod3(t *testing.T) { 115 | var ( 116 | name = "a-name" 117 | namespace = "a-namespace" 118 | image = "an-image" 119 | 120 | wantPod = &corev1.Pod{ 121 | ObjectMeta: metav1.ObjectMeta{ 122 | Name: "a-name", 123 | }, 124 | Spec: corev1.PodSpec{ 125 | Containers: []corev1.Container{ 126 | { 127 | Name: "runtime", 128 | Image: "an-image", 129 | }, 130 | }, 131 | }, 132 | } 133 | 134 | wantActions = 1 135 | ) 136 | 137 | clientset := fake.NewSimpleClientset() // ➊ 138 | _, _ = CreatePod( // ➋ 139 | context.Background(), 140 | clientset, 141 | name, 142 | namespace, 143 | image, 144 | ) 145 | 146 | actions := clientset.Actions() // ➌ 147 | if len(actions) != wantActions { // ➍ 148 | t.Errorf("# actions = %d, want %d", 149 | len(actions), 150 | wantActions, 151 | ) 152 | } 153 | action := actions[0] // ➎ 154 | 155 | actionNamespace := action.GetNamespace() // ➏ 156 | if actionNamespace != namespace { 157 | t.Errorf("action namespace = %s, want %s", 158 | actionNamespace, 159 | namespace, 160 | ) 161 | } 162 | 163 | if !action.Matches("create", "pods") { // ➐ 164 | t.Errorf("action verb = %s, want create", 165 | action.GetVerb(), 166 | ) 167 | t.Errorf("action resource = %s, want pods", 168 | action.GetResource().Resource, 169 | ) 170 | } 171 | 172 | createAction := action.(ktesting.CreateAction) // ➑ 173 | obj := createAction.GetObject() // ➒ 174 | if !reflect.DeepEqual(obj, wantPod) { 175 | t.Errorf("create action object = %v, want %v", 176 | obj, 177 | wantPod, 178 | ) 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /ch07/rest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | "k8s.io/client-go/rest" 8 | ) 9 | 10 | func getPods( 11 | ctx context.Context, 12 | restClient rest.Interface, 13 | ns string, 14 | ) ([]corev1.Pod, error) { 15 | result := corev1.PodList{} 16 | err := restClient.Get(). 17 | Namespace(ns). 18 | Resource("pods"). 19 | Do(ctx). 20 | Into(&result) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return result.Items, nil 25 | } 26 | -------------------------------------------------------------------------------- /ch07/rest_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | "net/url" 8 | "testing" 9 | 10 | corev1 "k8s.io/api/core/v1" 11 | kerrors "k8s.io/apimachinery/pkg/api/errors" 12 | "k8s.io/client-go/kubernetes/scheme" 13 | "k8s.io/client-go/rest/fake" 14 | ) 15 | 16 | func Test_getPods1(t *testing.T) { 17 | restClient := &fake.RESTClient{ 18 | GroupVersion: corev1.SchemeGroupVersion, 19 | NegotiatedSerializer: scheme.Codecs, 20 | 21 | Err: errors.New("an error from the rest client"), 22 | } 23 | 24 | _, err := getPods( 25 | context.Background(), 26 | restClient, 27 | "default", 28 | ) 29 | 30 | status, ok := err.(*url.Error) 31 | if !ok { 32 | t.Errorf("err should be of type url.Error") 33 | } 34 | if status.Err.Error() != errors.New(`an error from the rest client`).Error() { 35 | t.Errorf("Error is %v\n", status.Err.Error()) 36 | } 37 | } 38 | 39 | func Test_getPods2(t *testing.T) { 40 | restClient := &fake.RESTClient{ 41 | GroupVersion: corev1.SchemeGroupVersion, 42 | NegotiatedSerializer: scheme.Codecs, 43 | 44 | Err: nil, 45 | Resp: &http.Response{ 46 | StatusCode: http.StatusNotFound, 47 | }, 48 | } 49 | 50 | _, err := getPods( 51 | context.Background(), 52 | restClient, 53 | "default", 54 | ) 55 | 56 | status, ok := err.(*kerrors.StatusError) 57 | if !ok { 58 | t.Errorf("err should be of type errors.StatusError") 59 | } 60 | code := status.Status().Code 61 | if code != http.StatusNotFound { 62 | t.Errorf("Error code must be %d but is %d\n", http.StatusNotFound, code) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ch08/crd-with-cols.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: myresources.mygroup.example.com 5 | spec: 6 | group: mygroup.example.com 7 | scope: Namespaced 8 | names: 9 | plural: myresources 10 | singular: myresource 11 | shortNames: 12 | - my 13 | - myres 14 | kind: MyResource 15 | categories: 16 | - all 17 | versions: 18 | - name: v1alpha1 19 | served: true 20 | storage: true 21 | subresources: 22 | status: {} 23 | schema: 24 | openAPIV3Schema: 25 | type: object 26 | properties: 27 | spec: 28 | type: object 29 | properties: 30 | image: 31 | type: string 32 | memory: 33 | x-kubernetes-int-or-string: true 34 | status: 35 | type: object 36 | properties: 37 | state: 38 | type: string 39 | additionalPrinterColumns: 40 | - name: image 41 | jsonPath: .spec.image 42 | type: string 43 | - name: memory 44 | jsonPath: .spec.memory 45 | type: string 46 | - name: age 47 | jsonPath: .metadata.creationTimestamp 48 | type: date 49 | 50 | -------------------------------------------------------------------------------- /ch08/crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: myresources.mygroup.example.com 5 | spec: 6 | group: mygroup.example.com 7 | scope: Namespaced 8 | names: 9 | plural: myresources 10 | singular: myresource 11 | shortNames: 12 | - my 13 | - myres 14 | kind: MyResource 15 | categories: 16 | - all 17 | versions: 18 | - name: v1alpha1 19 | served: true 20 | storage: true 21 | schema: 22 | openAPIV3Schema: 23 | type: object 24 | -------------------------------------------------------------------------------- /ch08/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch8 2 | 3 | go 1.19 4 | 5 | require ( 6 | k8s.io/apiextensions-apiserver v0.25.3 7 | k8s.io/apimachinery v0.25.3 8 | k8s.io/client-go v0.25.3 9 | ) 10 | 11 | require ( 12 | github.com/PuerkitoBio/purell v1.1.1 // indirect 13 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 16 | github.com/go-logr/logr v1.2.3 // indirect 17 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 18 | github.com/go-openapi/jsonreference v0.19.5 // indirect 19 | github.com/go-openapi/swag v0.19.14 // indirect 20 | github.com/gogo/protobuf v1.3.2 // indirect 21 | github.com/golang/protobuf v1.5.2 // indirect 22 | github.com/google/gnostic v0.5.7-v3refs // indirect 23 | github.com/google/gofuzz v1.1.0 // indirect 24 | github.com/imdario/mergo v0.3.6 // indirect 25 | github.com/josharian/intern v1.0.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/mailru/easyjson v0.7.6 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 31 | github.com/spf13/pflag v1.0.5 // indirect 32 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 33 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 34 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 35 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 36 | golang.org/x/text v0.3.7 // indirect 37 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 38 | google.golang.org/appengine v1.6.7 // indirect 39 | google.golang.org/protobuf v1.28.0 // indirect 40 | gopkg.in/inf.v0 v0.9.1 // indirect 41 | gopkg.in/yaml.v2 v2.4.0 // indirect 42 | gopkg.in/yaml.v3 v3.0.1 // indirect 43 | k8s.io/api v0.25.3 // indirect 44 | k8s.io/klog/v2 v2.70.1 // indirect 45 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 46 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 47 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 48 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 49 | sigs.k8s.io/yaml v1.2.0 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /ch08/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/client-go/rest" 10 | "k8s.io/client-go/tools/clientcmd" 11 | ) 12 | 13 | func main() { 14 | config, err := getConfig() 15 | if err != nil { 16 | panic(err) 17 | } 18 | clientset, err := clientset.NewForConfig(config) 19 | if err != nil { 20 | panic(err) 21 | } 22 | ctx := context.Background() 23 | list, err := clientset.ApiextensionsV1(). 24 | CustomResourceDefinitions(). 25 | List(ctx, metav1.ListOptions{}) 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | for _, crd := range list.Items { 31 | fmt.Printf("%s\n", crd.GetName()) 32 | } 33 | } 34 | 35 | func getConfig() (*rest.Config, error) { 36 | return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 37 | clientcmd.NewDefaultClientConfigLoadingRules(), 38 | nil, 39 | ).ClientConfig() 40 | } 41 | -------------------------------------------------------------------------------- /ch08/myres.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: mygroup.example.com/v1alpha1 2 | kind: MyResource 3 | metadata: 4 | name: myres1 5 | namespace: default 6 | spec: 7 | image: nginx 8 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/dynamic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | myresourcev1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 9 | "k8s.io/client-go/dynamic" 10 | ) 11 | 12 | func CreateMyResource( 13 | dynamicClient dynamic.Interface, 14 | u *unstructured.Unstructured, 15 | ) (*unstructured.Unstructured, error) { 16 | gvr := myresourcev1alpha1. 17 | SchemeGroupVersion. 18 | WithResource("myresources") 19 | return dynamicClient. 20 | Resource(gvr). 21 | Namespace("default"). 22 | Create( 23 | context.Background(), 24 | u, 25 | metav1.CreateOptions{}, 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/dynamic_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "k8s.io/apimachinery/pkg/runtime" 7 | "k8s.io/client-go/dynamic/fake" 8 | ktesting "k8s.io/client-go/testing" 9 | ) 10 | 11 | func TestCreateMyResourceWhenResourceExists(t *testing.T) { 12 | myres, err := getResource() 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | 17 | dynamicClient := fake.NewSimpleDynamicClient( 18 | runtime.NewScheme(), 19 | myres, 20 | ) 21 | 22 | // Not really used, just to show how to use it 23 | dynamicClient.Fake.PrependReactor( 24 | "create", 25 | "myresources", 26 | func( 27 | action ktesting.Action, 28 | ) (handled bool, ret runtime.Object, err error) { 29 | return false, nil, nil 30 | }) 31 | _, err = CreateMyResource(dynamicClient, myres) 32 | if err == nil { 33 | t.Error("Error should happen") 34 | } 35 | 36 | actions := dynamicClient.Fake.Actions() 37 | if len(actions) != 1 { 38 | t.Errorf("# of actions should be %d but is %d", 1, len(actions)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/myid/myresource-crd 2 | 3 | go 1.19 4 | 5 | require ( 6 | k8s.io/apimachinery v0.25.3 7 | k8s.io/client-go v0.25.3 8 | ) 9 | 10 | require ( 11 | github.com/PuerkitoBio/purell v1.1.1 // indirect 12 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 15 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 16 | github.com/go-logr/logr v1.2.3 // indirect 17 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 18 | github.com/go-openapi/jsonreference v0.19.5 // indirect 19 | github.com/go-openapi/swag v0.19.14 // indirect 20 | github.com/gogo/protobuf v1.3.2 // indirect 21 | github.com/golang/protobuf v1.5.2 // indirect 22 | github.com/google/gnostic v0.5.7-v3refs // indirect 23 | github.com/google/gofuzz v1.1.0 // indirect 24 | github.com/imdario/mergo v0.3.6 // indirect 25 | github.com/josharian/intern v1.0.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/mailru/easyjson v0.7.6 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 31 | github.com/pkg/errors v0.9.1 // indirect 32 | github.com/spf13/pflag v1.0.5 // indirect 33 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 34 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 35 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 36 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 37 | golang.org/x/text v0.3.7 // indirect 38 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 39 | google.golang.org/appengine v1.6.7 // indirect 40 | google.golang.org/protobuf v1.28.0 // indirect 41 | gopkg.in/inf.v0 v0.9.1 // indirect 42 | gopkg.in/yaml.v2 v2.4.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | k8s.io/api v0.25.3 // indirect 45 | k8s.io/klog/v2 v2.70.1 // indirect 46 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 47 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 48 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 49 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 50 | sigs.k8s.io/yaml v1.2.0 // indirect 51 | ) 52 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/hack/boilerplate.go.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Kubernetes-Programming-with-Go-by-Philippe-Martin/5dcb21203445f4c8aaea38bcd9c1e25d936ff0cf/ch09/github.com/myid/myresource-crd/hack/boilerplate.go.txt -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | myresourcev1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 8 | "github.com/myid/myresource-crd/pkg/clientset/clientset" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "k8s.io/client-go/dynamic" 13 | "k8s.io/client-go/tools/clientcmd" 14 | ) 15 | 16 | func main() { 17 | 18 | config, err := 19 | clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 20 | clientcmd.NewDefaultClientConfigLoadingRules(), 21 | nil, 22 | ).ClientConfig() 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | clientset, err := clientset.NewForConfig(config) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | list, err := clientset.MygroupV1alpha1(). 33 | MyResources("default"). 34 | List(context.Background(), metav1.ListOptions{}) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | for _, res := range list.Items { 40 | fmt.Printf("%s\n", res.GetName()) 41 | } 42 | 43 | // # Using the unstructured package and dynamic client 44 | // ## The Unstructured type 45 | u, err := getResource() 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | // ## Converting between typed and unstructured objects 51 | converter := runtime.DefaultUnstructuredConverter 52 | 53 | // ### Unstructured to Typed 54 | var myresource myresourcev1alpha1.MyResource 55 | converter.FromUnstructured( 56 | u.UnstructuredContent(), &myresource, 57 | ) 58 | 59 | fmt.Printf("%s, %s\n", myresource.Spec.Image, &myresource.Spec.Memory) 60 | 61 | // ### Typed to Unstructured 62 | var newU unstructured.Unstructured 63 | newU.Object, err = converter.ToUnstructured(&myresource) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | image, found, err := unstructured.NestedString(newU.Object, "spec", "image") 69 | if err != nil { 70 | panic(err) 71 | } 72 | if !found { 73 | panic("spec.image not found") 74 | } 75 | memory, found, err := unstructured.NestedString(newU.Object, "spec", "memory") 76 | if err != nil { 77 | panic(err) 78 | } 79 | if !found { 80 | panic("spec.memory not found") 81 | } 82 | fmt.Printf("%s, %s\n", image, memory) 83 | 84 | // # The dynamic client 85 | dynamicClient, err := dynamic.NewForConfig(config) 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | createdU, err := CreateMyResource(dynamicClient, u) 91 | if err != nil { 92 | panic(err) 93 | } 94 | _ = createdU 95 | } 96 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // pkg/apis/mygroup.example.com/v1alpha1/doc.go 2 | // +k8s:deepcopy-gen=package 3 | package v1alpha1 4 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | ) 8 | 9 | const GroupName = "mygroup.example.com" // ❶ 10 | 11 | var SchemeGroupVersion = schema.GroupVersion{ 12 | Group: GroupName, 13 | Version: "v1alpha1", // ❷ 14 | } 15 | 16 | var ( 17 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 18 | localSchemeBuilder = &SchemeBuilder 19 | AddToScheme = localSchemeBuilder.AddToScheme 20 | ) 21 | 22 | func addKnownTypes(scheme *runtime.Scheme) error { 23 | scheme.AddKnownTypes(SchemeGroupVersion, 24 | &MyResource{}, // ❸ 25 | &MyResourceList{}, 26 | ) 27 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/api/resource" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 9 | // +genclient 10 | type MyResource struct { 11 | metav1.TypeMeta `json:",inline"` 12 | metav1.ObjectMeta `json:"metadata,omitempty"` 13 | 14 | Spec MyResourceSpec `json:"spec"` 15 | Status MyResourceStatus `json:"status"` 16 | } 17 | 18 | type MyResourceSpec struct { 19 | Image string `json:"image"` 20 | Memory resource.Quantity `json:"memory"` 21 | } 22 | 23 | type MyResourceStatus struct { 24 | State string `json:"state"` 25 | } 26 | 27 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 28 | type MyResourceList struct { 29 | metav1.TypeMeta `json:",inline"` 30 | metav1.ListMeta `json:"metadata,omitempty"` 31 | 32 | Items []MyResource `json:"items"` 33 | } 34 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | // Code generated by deepcopy-gen. DO NOT EDIT. 5 | 6 | package v1alpha1 7 | 8 | import ( 9 | runtime "k8s.io/apimachinery/pkg/runtime" 10 | ) 11 | 12 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 13 | func (in *MyResource) DeepCopyInto(out *MyResource) { 14 | *out = *in 15 | out.TypeMeta = in.TypeMeta 16 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 17 | in.Spec.DeepCopyInto(&out.Spec) 18 | out.Status = in.Status 19 | return 20 | } 21 | 22 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResource. 23 | func (in *MyResource) DeepCopy() *MyResource { 24 | if in == nil { 25 | return nil 26 | } 27 | out := new(MyResource) 28 | in.DeepCopyInto(out) 29 | return out 30 | } 31 | 32 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 33 | func (in *MyResource) DeepCopyObject() runtime.Object { 34 | if c := in.DeepCopy(); c != nil { 35 | return c 36 | } 37 | return nil 38 | } 39 | 40 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 41 | func (in *MyResourceList) DeepCopyInto(out *MyResourceList) { 42 | *out = *in 43 | out.TypeMeta = in.TypeMeta 44 | in.ListMeta.DeepCopyInto(&out.ListMeta) 45 | if in.Items != nil { 46 | in, out := &in.Items, &out.Items 47 | *out = make([]MyResource, len(*in)) 48 | for i := range *in { 49 | (*in)[i].DeepCopyInto(&(*out)[i]) 50 | } 51 | } 52 | return 53 | } 54 | 55 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResourceList. 56 | func (in *MyResourceList) DeepCopy() *MyResourceList { 57 | if in == nil { 58 | return nil 59 | } 60 | out := new(MyResourceList) 61 | in.DeepCopyInto(out) 62 | return out 63 | } 64 | 65 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 66 | func (in *MyResourceList) DeepCopyObject() runtime.Object { 67 | if c := in.DeepCopy(); c != nil { 68 | return c 69 | } 70 | return nil 71 | } 72 | 73 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 74 | func (in *MyResourceSpec) DeepCopyInto(out *MyResourceSpec) { 75 | *out = *in 76 | out.Memory = in.Memory.DeepCopy() 77 | return 78 | } 79 | 80 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResourceSpec. 81 | func (in *MyResourceSpec) DeepCopy() *MyResourceSpec { 82 | if in == nil { 83 | return nil 84 | } 85 | out := new(MyResourceSpec) 86 | in.DeepCopyInto(out) 87 | return out 88 | } 89 | 90 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 91 | func (in *MyResourceStatus) DeepCopyInto(out *MyResourceStatus) { 92 | *out = *in 93 | return 94 | } 95 | 96 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResourceStatus. 97 | func (in *MyResourceStatus) DeepCopy() *MyResourceStatus { 98 | if in == nil { 99 | return nil 100 | } 101 | out := new(MyResourceStatus) 102 | in.DeepCopyInto(out) 103 | return out 104 | } 105 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/clientset.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package clientset 4 | 5 | import ( 6 | "fmt" 7 | "net/http" 8 | 9 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/clientset/clientset/typed/mygroup.example.com/v1alpha1" 10 | discovery "k8s.io/client-go/discovery" 11 | rest "k8s.io/client-go/rest" 12 | flowcontrol "k8s.io/client-go/util/flowcontrol" 13 | ) 14 | 15 | type Interface interface { 16 | Discovery() discovery.DiscoveryInterface 17 | MygroupV1alpha1() mygroupv1alpha1.MygroupV1alpha1Interface 18 | } 19 | 20 | // Clientset contains the clients for groups. Each group has exactly one 21 | // version included in a Clientset. 22 | type Clientset struct { 23 | *discovery.DiscoveryClient 24 | mygroupV1alpha1 *mygroupv1alpha1.MygroupV1alpha1Client 25 | } 26 | 27 | // MygroupV1alpha1 retrieves the MygroupV1alpha1Client 28 | func (c *Clientset) MygroupV1alpha1() mygroupv1alpha1.MygroupV1alpha1Interface { 29 | return c.mygroupV1alpha1 30 | } 31 | 32 | // Discovery retrieves the DiscoveryClient 33 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 34 | if c == nil { 35 | return nil 36 | } 37 | return c.DiscoveryClient 38 | } 39 | 40 | // NewForConfig creates a new Clientset for the given config. 41 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 42 | // NewForConfig will generate a rate-limiter in configShallowCopy. 43 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 44 | // where httpClient was generated with rest.HTTPClientFor(c). 45 | func NewForConfig(c *rest.Config) (*Clientset, error) { 46 | configShallowCopy := *c 47 | 48 | if configShallowCopy.UserAgent == "" { 49 | configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() 50 | } 51 | 52 | // share the transport between all clients 53 | httpClient, err := rest.HTTPClientFor(&configShallowCopy) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | return NewForConfigAndClient(&configShallowCopy, httpClient) 59 | } 60 | 61 | // NewForConfigAndClient creates a new Clientset for the given config and http client. 62 | // Note the http client provided takes precedence over the configured transport values. 63 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 64 | // NewForConfigAndClient will generate a rate-limiter in configShallowCopy. 65 | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { 66 | configShallowCopy := *c 67 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 68 | if configShallowCopy.Burst <= 0 { 69 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 70 | } 71 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 72 | } 73 | 74 | var cs Clientset 75 | var err error 76 | cs.mygroupV1alpha1, err = mygroupv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) 82 | if err != nil { 83 | return nil, err 84 | } 85 | return &cs, nil 86 | } 87 | 88 | // NewForConfigOrDie creates a new Clientset for the given config and 89 | // panics if there is an error in the config. 90 | func NewForConfigOrDie(c *rest.Config) *Clientset { 91 | cs, err := NewForConfig(c) 92 | if err != nil { 93 | panic(err) 94 | } 95 | return cs 96 | } 97 | 98 | // New creates a new Clientset for the given RESTClient. 99 | func New(c rest.Interface) *Clientset { 100 | var cs Clientset 101 | cs.mygroupV1alpha1 = mygroupv1alpha1.New(c) 102 | 103 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 104 | return &cs 105 | } 106 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated clientset. 4 | package clientset 5 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | clientset "github.com/myid/myresource-crd/pkg/clientset/clientset" 7 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/clientset/clientset/typed/mygroup.example.com/v1alpha1" 8 | fakemygroupv1alpha1 "github.com/myid/myresource-crd/pkg/clientset/clientset/typed/mygroup.example.com/v1alpha1/fake" 9 | "k8s.io/apimachinery/pkg/runtime" 10 | "k8s.io/apimachinery/pkg/watch" 11 | "k8s.io/client-go/discovery" 12 | fakediscovery "k8s.io/client-go/discovery/fake" 13 | "k8s.io/client-go/testing" 14 | ) 15 | 16 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 17 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 18 | // without applying any validations and/or defaults. It shouldn't be considered a replacement 19 | // for a real clientset and is mostly useful in simple unit tests. 20 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 21 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 22 | for _, obj := range objects { 23 | if err := o.Add(obj); err != nil { 24 | panic(err) 25 | } 26 | } 27 | 28 | cs := &Clientset{tracker: o} 29 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 30 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 31 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 32 | gvr := action.GetResource() 33 | ns := action.GetNamespace() 34 | watch, err := o.Watch(gvr, ns) 35 | if err != nil { 36 | return false, nil, err 37 | } 38 | return true, watch, nil 39 | }) 40 | 41 | return cs 42 | } 43 | 44 | // Clientset implements clientset.Interface. Meant to be embedded into a 45 | // struct to get a default implementation. This makes faking out just the method 46 | // you want to test easier. 47 | type Clientset struct { 48 | testing.Fake 49 | discovery *fakediscovery.FakeDiscovery 50 | tracker testing.ObjectTracker 51 | } 52 | 53 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 54 | return c.discovery 55 | } 56 | 57 | func (c *Clientset) Tracker() testing.ObjectTracker { 58 | return c.tracker 59 | } 60 | 61 | var ( 62 | _ clientset.Interface = &Clientset{} 63 | _ testing.FakeClient = &Clientset{} 64 | ) 65 | 66 | // MygroupV1alpha1 retrieves the MygroupV1alpha1Client 67 | func (c *Clientset) MygroupV1alpha1() mygroupv1alpha1.MygroupV1alpha1Interface { 68 | return &fakemygroupv1alpha1.FakeMygroupV1alpha1{Fake: &c.Fake} 69 | } 70 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated fake clientset. 4 | package fake 5 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/fake/register.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | runtime "k8s.io/apimachinery/pkg/runtime" 9 | schema "k8s.io/apimachinery/pkg/runtime/schema" 10 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 12 | ) 13 | 14 | var scheme = runtime.NewScheme() 15 | var codecs = serializer.NewCodecFactory(scheme) 16 | 17 | var localSchemeBuilder = runtime.SchemeBuilder{ 18 | mygroupv1alpha1.AddToScheme, 19 | } 20 | 21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 22 | // of clientsets, like in: 23 | // 24 | // import ( 25 | // "k8s.io/client-go/kubernetes" 26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 28 | // ) 29 | // 30 | // kclientset, _ := kubernetes.NewForConfig(c) 31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 32 | // 33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 34 | // correctly. 35 | var AddToScheme = localSchemeBuilder.AddToScheme 36 | 37 | func init() { 38 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 39 | utilruntime.Must(AddToScheme(scheme)) 40 | } 41 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/scheme/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package contains the scheme of the automatically generated clientset. 4 | package scheme 5 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/scheme/register.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package scheme 4 | 5 | import ( 6 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | runtime "k8s.io/apimachinery/pkg/runtime" 9 | schema "k8s.io/apimachinery/pkg/runtime/schema" 10 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 12 | ) 13 | 14 | var Scheme = runtime.NewScheme() 15 | var Codecs = serializer.NewCodecFactory(Scheme) 16 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 17 | var localSchemeBuilder = runtime.SchemeBuilder{ 18 | mygroupv1alpha1.AddToScheme, 19 | } 20 | 21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 22 | // of clientsets, like in: 23 | // 24 | // import ( 25 | // "k8s.io/client-go/kubernetes" 26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 28 | // ) 29 | // 30 | // kclientset, _ := kubernetes.NewForConfig(c) 31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 32 | // 33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 34 | // correctly. 35 | var AddToScheme = localSchemeBuilder.AddToScheme 36 | 37 | func init() { 38 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 39 | utilruntime.Must(AddToScheme(Scheme)) 40 | } 41 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/typed/mygroup.example.com/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated typed clients. 4 | package v1alpha1 5 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/typed/mygroup.example.com/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // Package fake has the automatically generated clients. 4 | package fake 5 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/typed/mygroup.example.com/v1alpha1/fake/fake_mygroup.example.com_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | v1alpha1 "github.com/myid/myresource-crd/pkg/clientset/clientset/typed/mygroup.example.com/v1alpha1" 7 | rest "k8s.io/client-go/rest" 8 | testing "k8s.io/client-go/testing" 9 | ) 10 | 11 | type FakeMygroupV1alpha1 struct { 12 | *testing.Fake 13 | } 14 | 15 | func (c *FakeMygroupV1alpha1) MyResources(namespace string) v1alpha1.MyResourceInterface { 16 | return &FakeMyResources{c, namespace} 17 | } 18 | 19 | // RESTClient returns a RESTClient that is used to communicate 20 | // with API server by this client implementation. 21 | func (c *FakeMygroupV1alpha1) RESTClient() rest.Interface { 22 | var ret *rest.RESTClient 23 | return ret 24 | } 25 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/typed/mygroup.example.com/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | type MyResourceExpansion interface{} 6 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/pkg/clientset/clientset/typed/mygroup.example.com/v1alpha1/mygroup.example.com_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | "net/http" 7 | 8 | v1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 9 | "github.com/myid/myresource-crd/pkg/clientset/clientset/scheme" 10 | rest "k8s.io/client-go/rest" 11 | ) 12 | 13 | type MygroupV1alpha1Interface interface { 14 | RESTClient() rest.Interface 15 | MyResourcesGetter 16 | } 17 | 18 | // MygroupV1alpha1Client is used to interact with features provided by the mygroup.example.com group. 19 | type MygroupV1alpha1Client struct { 20 | restClient rest.Interface 21 | } 22 | 23 | func (c *MygroupV1alpha1Client) MyResources(namespace string) MyResourceInterface { 24 | return newMyResources(c, namespace) 25 | } 26 | 27 | // NewForConfig creates a new MygroupV1alpha1Client for the given config. 28 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 29 | // where httpClient was generated with rest.HTTPClientFor(c). 30 | func NewForConfig(c *rest.Config) (*MygroupV1alpha1Client, error) { 31 | config := *c 32 | if err := setConfigDefaults(&config); err != nil { 33 | return nil, err 34 | } 35 | httpClient, err := rest.HTTPClientFor(&config) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return NewForConfigAndClient(&config, httpClient) 40 | } 41 | 42 | // NewForConfigAndClient creates a new MygroupV1alpha1Client for the given config and http client. 43 | // Note the http client provided takes precedence over the configured transport values. 44 | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*MygroupV1alpha1Client, error) { 45 | config := *c 46 | if err := setConfigDefaults(&config); err != nil { 47 | return nil, err 48 | } 49 | client, err := rest.RESTClientForConfigAndClient(&config, h) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &MygroupV1alpha1Client{client}, nil 54 | } 55 | 56 | // NewForConfigOrDie creates a new MygroupV1alpha1Client for the given config and 57 | // panics if there is an error in the config. 58 | func NewForConfigOrDie(c *rest.Config) *MygroupV1alpha1Client { 59 | client, err := NewForConfig(c) 60 | if err != nil { 61 | panic(err) 62 | } 63 | return client 64 | } 65 | 66 | // New creates a new MygroupV1alpha1Client for the given RESTClient. 67 | func New(c rest.Interface) *MygroupV1alpha1Client { 68 | return &MygroupV1alpha1Client{c} 69 | } 70 | 71 | func setConfigDefaults(config *rest.Config) error { 72 | gv := v1alpha1.SchemeGroupVersion 73 | config.GroupVersion = &gv 74 | config.APIPath = "/apis" 75 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 76 | 77 | if config.UserAgent == "" { 78 | config.UserAgent = rest.DefaultKubernetesUserAgent() 79 | } 80 | 81 | return nil 82 | } 83 | 84 | // RESTClient returns a RESTClient that is used to communicate 85 | // with API server by this client implementation. 86 | func (c *MygroupV1alpha1Client) RESTClient() rest.Interface { 87 | if c == nil { 88 | return nil 89 | } 90 | return c.restClient 91 | } 92 | -------------------------------------------------------------------------------- /ch09/github.com/myid/myresource-crd/unstructured.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | myresourcev1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 5 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 6 | ) 7 | 8 | func getResource() (*unstructured.Unstructured, error) { 9 | myres := unstructured.Unstructured{} 10 | myres.SetGroupVersionKind( 11 | myresourcev1alpha1.SchemeGroupVersion. 12 | WithKind("MyResource")) 13 | myres.SetName("myres1") 14 | myres.SetNamespace("default") 15 | err := unstructured.SetNestedField( 16 | myres.Object, 17 | "nginx", 18 | "spec", "image", 19 | ) 20 | if err != nil { 21 | return nil, err 22 | } 23 | // Use int64 24 | err = unstructured.SetNestedField( 25 | myres.Object, 26 | int64(1024*1024*1024), 27 | "spec", "memory", 28 | ) 29 | if err != nil { 30 | return nil, err 31 | } 32 | // or use string 33 | err = unstructured.SetNestedField( 34 | myres.Object, 35 | "1024Mi", 36 | "spec", "memory", 37 | ) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return &myres, nil 42 | } 43 | -------------------------------------------------------------------------------- /ch10/client/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch10/client 2 | 3 | go 1.19 4 | 5 | replace github.com/myid/myresource-crd => ../../ch09/github.com/myid/myresource-crd 6 | 7 | require ( 8 | github.com/myid/myresource-crd v0.0.0-00010101000000-000000000000 9 | k8s.io/api v0.25.3 10 | k8s.io/apimachinery v0.25.3 11 | k8s.io/client-go v0.25.3 12 | sigs.k8s.io/controller-runtime v0.13.0 13 | ) 14 | 15 | require ( 16 | github.com/PuerkitoBio/purell v1.1.1 // indirect 17 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 23 | github.com/fsnotify/fsnotify v1.5.4 // indirect 24 | github.com/go-logr/logr v1.2.3 // indirect 25 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 26 | github.com/go-openapi/jsonreference v0.19.5 // indirect 27 | github.com/go-openapi/swag v0.19.14 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 30 | github.com/golang/protobuf v1.5.2 // indirect 31 | github.com/google/gnostic v0.5.7-v3refs // indirect 32 | github.com/google/go-cmp v0.5.8 // indirect 33 | github.com/google/gofuzz v1.1.0 // indirect 34 | github.com/google/uuid v1.1.2 // indirect 35 | github.com/imdario/mergo v0.3.12 // indirect 36 | github.com/josharian/intern v1.0.0 // indirect 37 | github.com/json-iterator/go v1.1.12 // indirect 38 | github.com/mailru/easyjson v0.7.6 // indirect 39 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 43 | github.com/pkg/errors v0.9.1 // indirect 44 | github.com/prometheus/client_golang v1.12.2 // indirect 45 | github.com/prometheus/client_model v0.2.0 // indirect 46 | github.com/prometheus/common v0.32.1 // indirect 47 | github.com/prometheus/procfs v0.7.3 // indirect 48 | github.com/spf13/pflag v1.0.5 // indirect 49 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 50 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 51 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 52 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 53 | golang.org/x/text v0.3.7 // indirect 54 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 55 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 56 | google.golang.org/appengine v1.6.7 // indirect 57 | google.golang.org/protobuf v1.28.0 // indirect 58 | gopkg.in/inf.v0 v0.9.1 // indirect 59 | gopkg.in/yaml.v2 v2.4.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | k8s.io/apiextensions-apiserver v0.25.0 // indirect 62 | k8s.io/component-base v0.25.0 // indirect 63 | k8s.io/klog/v2 v2.70.1 // indirect 64 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 65 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 66 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 67 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 68 | sigs.k8s.io/yaml v1.3.0 // indirect 69 | ) 70 | -------------------------------------------------------------------------------- /ch10/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | appsv1 "k8s.io/api/apps/v1" 8 | corev1 "k8s.io/api/core/v1" 9 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 12 | "sigs.k8s.io/controller-runtime/pkg/builder" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | "sigs.k8s.io/controller-runtime/pkg/client/config" 15 | "sigs.k8s.io/controller-runtime/pkg/manager" 16 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 17 | 18 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 19 | ) 20 | 21 | func main() { 22 | 23 | scheme := runtime.NewScheme() 24 | clientgoscheme.AddToScheme(scheme) 25 | mygroupv1alpha1.AddToScheme(scheme) 26 | 27 | mgr, err := manager.New( 28 | config.GetConfigOrDie(), 29 | manager.Options{ 30 | Scheme: scheme, 31 | }, 32 | ) 33 | panicIf(err) 34 | 35 | err = builder. 36 | ControllerManagedBy(mgr). 37 | For(&mygroupv1alpha1.MyResource{}). 38 | Owns(&corev1.Pod{}). 39 | Complete(&MyReconciler{}) 40 | panicIf(err) 41 | 42 | err = mgr.Start(context.Background()) 43 | panicIf(err) 44 | } 45 | 46 | type MyReconciler struct { 47 | client client.Client 48 | } 49 | 50 | func (a *MyReconciler) Reconcile( 51 | ctx context.Context, 52 | req reconcile.Request, 53 | ) (reconcile.Result, error) { 54 | fmt.Printf("reconcile %v\n", req) 55 | 56 | // ## Getting information about a resource 57 | myresource := mygroupv1alpha1.MyResource{} 58 | err := a.client.Get( 59 | ctx, 60 | req.NamespacedName, 61 | &myresource, 62 | ) 63 | if err != nil { 64 | fmt.Printf("%v\n", err) 65 | return reconcile.Result{}, err 66 | } 67 | 68 | // ## Listing resources 69 | resourcesList := mygroupv1alpha1.MyResourceList{} 70 | err = a.client.List(ctx, &resourcesList, client.InNamespace(req.Namespace)) 71 | if err != nil { 72 | fmt.Printf("%v\n", err) 73 | return reconcile.Result{}, err 74 | } 75 | for _, res := range resourcesList.Items { 76 | fmt.Printf("res: %s\n", res.GetName()) 77 | } 78 | 79 | // ## Creating a resource 80 | podToCreate := corev1.Pod{ 81 | Spec: corev1.PodSpec{ 82 | Containers: []corev1.Container{ 83 | { 84 | Name: "main", 85 | Image: "nginx", 86 | }, 87 | }, 88 | }, 89 | } 90 | podToCreate.SetName(req.Name) 91 | podToCreate.SetNamespace(req.Namespace) 92 | err = a.client.Create(ctx, &podToCreate) 93 | if err != nil { 94 | fmt.Printf("create: %v\n", err) 95 | return reconcile.Result{}, err 96 | } 97 | 98 | // ## Deleting a resource 99 | podToDelete := corev1.Pod{} 100 | podToDelete.SetName(req.Name) 101 | podToDelete.SetNamespace(req.Namespace) 102 | err = a.client.Delete(ctx, &podToDelete) 103 | if err != nil { 104 | fmt.Printf("delete: %v\n", err) 105 | return reconcile.Result{}, err 106 | } 107 | 108 | // ## Deleting a collection of resources 109 | err = a.client.DeleteAllOf( 110 | ctx, 111 | &corev1.Pod{}, 112 | client.InNamespace(req.Namespace)) 113 | if err != nil { 114 | fmt.Printf("deleteAllOf: %v\n", err) 115 | // return reconcile.Result{}, err 116 | } 117 | 118 | // ## Patching a resource 119 | // ### Server-Side Apply 120 | deployToApply := appsv1.Deployment{ 121 | Spec: appsv1.DeploymentSpec{ 122 | Selector: &v1.LabelSelector{ 123 | MatchLabels: map[string]string{ 124 | "app": "app1", 125 | }, 126 | }, 127 | Template: corev1.PodTemplateSpec{ 128 | ObjectMeta: v1.ObjectMeta{ 129 | Labels: map[string]string{ 130 | "app": "app1", 131 | }, 132 | }, 133 | Spec: corev1.PodSpec{ 134 | Containers: []corev1.Container{ 135 | { 136 | Name: "main", 137 | Image: "nginx", 138 | }, 139 | }, 140 | }, 141 | }, 142 | }, 143 | } 144 | deployToApply.SetName("nginx") 145 | deployToApply.SetNamespace("default") 146 | deployToApply.SetGroupVersionKind( 147 | appsv1.SchemeGroupVersion.WithKind("Deployment"), 148 | ) 149 | err = a.client.Patch( 150 | ctx, 151 | &deployToApply, 152 | client.Apply, 153 | client.FieldOwner("mycontroller"), 154 | client.ForceOwnership, 155 | ) 156 | if err != nil { 157 | fmt.Printf("patch: %v\n", err) 158 | return reconcile.Result{}, err 159 | } 160 | 161 | // ## Updating the status of a resource 162 | myresource.Status.State = "done" 163 | err = a.client.Status().Update(ctx, &myresource) 164 | if err != nil { 165 | fmt.Printf("status.update: %v\n", err) 166 | return reconcile.Result{}, err 167 | } 168 | 169 | return reconcile.Result{}, nil 170 | } 171 | 172 | func (a *MyReconciler) InjectClient( 173 | c client.Client, 174 | ) error { 175 | a.client = c 176 | return nil 177 | } 178 | 179 | func panicIf(err error) { 180 | if err != nil { 181 | panic(err) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /ch10/events/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch10/events 2 | 3 | go 1.19 4 | 5 | replace github.com/myid/myresource-crd => ../../ch09/github.com/myid/myresource-crd 6 | 7 | require ( 8 | github.com/myid/myresource-crd v0.0.0-00010101000000-000000000000 9 | k8s.io/api v0.25.3 10 | k8s.io/apimachinery v0.25.3 11 | k8s.io/client-go v0.25.3 12 | sigs.k8s.io/controller-runtime v0.13.0 13 | ) 14 | 15 | require ( 16 | github.com/PuerkitoBio/purell v1.1.1 // indirect 17 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 23 | github.com/fsnotify/fsnotify v1.5.4 // indirect 24 | github.com/go-logr/logr v1.2.3 // indirect 25 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 26 | github.com/go-openapi/jsonreference v0.19.5 // indirect 27 | github.com/go-openapi/swag v0.19.14 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 30 | github.com/golang/protobuf v1.5.2 // indirect 31 | github.com/google/gnostic v0.5.7-v3refs // indirect 32 | github.com/google/go-cmp v0.5.8 // indirect 33 | github.com/google/gofuzz v1.1.0 // indirect 34 | github.com/google/uuid v1.1.2 // indirect 35 | github.com/imdario/mergo v0.3.12 // indirect 36 | github.com/josharian/intern v1.0.0 // indirect 37 | github.com/json-iterator/go v1.1.12 // indirect 38 | github.com/mailru/easyjson v0.7.6 // indirect 39 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 43 | github.com/pkg/errors v0.9.1 // indirect 44 | github.com/prometheus/client_golang v1.12.2 // indirect 45 | github.com/prometheus/client_model v0.2.0 // indirect 46 | github.com/prometheus/common v0.32.1 // indirect 47 | github.com/prometheus/procfs v0.7.3 // indirect 48 | github.com/spf13/pflag v1.0.5 // indirect 49 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 50 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 51 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 52 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 53 | golang.org/x/text v0.3.7 // indirect 54 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 55 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 56 | google.golang.org/appengine v1.6.7 // indirect 57 | google.golang.org/protobuf v1.28.0 // indirect 58 | gopkg.in/inf.v0 v0.9.1 // indirect 59 | gopkg.in/yaml.v2 v2.4.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | k8s.io/apiextensions-apiserver v0.25.0 // indirect 62 | k8s.io/component-base v0.25.0 // indirect 63 | k8s.io/klog/v2 v2.70.1 // indirect 64 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 65 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 66 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 67 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 68 | sigs.k8s.io/yaml v1.3.0 // indirect 69 | ) 70 | -------------------------------------------------------------------------------- /ch10/events/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 10 | "k8s.io/client-go/tools/record" 11 | "sigs.k8s.io/controller-runtime/pkg/builder" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | "sigs.k8s.io/controller-runtime/pkg/client/config" 14 | "sigs.k8s.io/controller-runtime/pkg/manager" 15 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 16 | 17 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 18 | ) 19 | 20 | func main() { 21 | 22 | scheme := runtime.NewScheme() 23 | clientgoscheme.AddToScheme(scheme) 24 | mygroupv1alpha1.AddToScheme(scheme) 25 | 26 | mgr, err := manager.New( 27 | config.GetConfigOrDie(), 28 | manager.Options{ 29 | Scheme: scheme, 30 | }, 31 | ) 32 | panicIf(err) 33 | 34 | eventRecorder := mgr.GetEventRecorderFor( 35 | "MyResource", 36 | ) 37 | 38 | err = builder. 39 | ControllerManagedBy(mgr). 40 | For(&mygroupv1alpha1.MyResource{}). 41 | Owns(&corev1.Pod{}). 42 | Complete(&MyReconciler{ 43 | EventRecorder: eventRecorder, 44 | }) 45 | panicIf(err) 46 | 47 | err = mgr.Start(context.Background()) 48 | panicIf(err) 49 | } 50 | 51 | type MyReconciler struct { 52 | client client.Client 53 | EventRecorder record.EventRecorder 54 | } 55 | 56 | func (a *MyReconciler) Reconcile( 57 | ctx context.Context, 58 | req reconcile.Request, 59 | ) (reconcile.Result, error) { 60 | fmt.Printf("reconcile %v\n", req) 61 | 62 | myresource := mygroupv1alpha1.MyResource{} 63 | err := a.client.Get( 64 | ctx, 65 | req.NamespacedName, 66 | &myresource, 67 | ) 68 | if err != nil { 69 | fmt.Printf("%v\n", err) 70 | return reconcile.Result{}, err 71 | } 72 | 73 | a.EventRecorder.Event(&myresource, corev1.EventTypeNormal, "Reconcile", "reconciling") 74 | 75 | return reconcile.Result{}, nil 76 | } 77 | 78 | func (a *MyReconciler) InjectClient( 79 | c client.Client, 80 | ) error { 81 | a.client = c 82 | return nil 83 | } 84 | 85 | func panicIf(err error) { 86 | if err != nil { 87 | panic(err) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ch10/ex1/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch10/ex1 2 | 3 | go 1.19 4 | 5 | replace github.com/myid/myresource-crd => ../../ch09/github.com/myid/myresource-crd 6 | 7 | require ( 8 | github.com/myid/myresource-crd v0.0.0-00010101000000-000000000000 9 | k8s.io/api v0.25.3 10 | k8s.io/apimachinery v0.25.3 11 | k8s.io/client-go v0.25.3 12 | sigs.k8s.io/controller-runtime v0.13.0 13 | ) 14 | 15 | require ( 16 | github.com/PuerkitoBio/purell v1.1.1 // indirect 17 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 23 | github.com/fsnotify/fsnotify v1.5.4 // indirect 24 | github.com/go-logr/logr v1.2.3 // indirect 25 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 26 | github.com/go-openapi/jsonreference v0.19.5 // indirect 27 | github.com/go-openapi/swag v0.19.14 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 30 | github.com/golang/protobuf v1.5.2 // indirect 31 | github.com/google/gnostic v0.5.7-v3refs // indirect 32 | github.com/google/go-cmp v0.5.8 // indirect 33 | github.com/google/gofuzz v1.1.0 // indirect 34 | github.com/google/uuid v1.1.2 // indirect 35 | github.com/imdario/mergo v0.3.12 // indirect 36 | github.com/josharian/intern v1.0.0 // indirect 37 | github.com/json-iterator/go v1.1.12 // indirect 38 | github.com/mailru/easyjson v0.7.6 // indirect 39 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 43 | github.com/pkg/errors v0.9.1 // indirect 44 | github.com/prometheus/client_golang v1.12.2 // indirect 45 | github.com/prometheus/client_model v0.2.0 // indirect 46 | github.com/prometheus/common v0.32.1 // indirect 47 | github.com/prometheus/procfs v0.7.3 // indirect 48 | github.com/spf13/pflag v1.0.5 // indirect 49 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 50 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 51 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 52 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 53 | golang.org/x/text v0.3.7 // indirect 54 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 55 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 56 | google.golang.org/appengine v1.6.7 // indirect 57 | google.golang.org/protobuf v1.28.0 // indirect 58 | gopkg.in/inf.v0 v0.9.1 // indirect 59 | gopkg.in/yaml.v2 v2.4.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | k8s.io/component-base v0.25.0 // indirect 62 | k8s.io/klog/v2 v2.70.1 // indirect 63 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 64 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 65 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 66 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 67 | sigs.k8s.io/yaml v1.3.0 // indirect 68 | ) 69 | -------------------------------------------------------------------------------- /ch10/ex1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 10 | "sigs.k8s.io/controller-runtime/pkg/client/config" 11 | "sigs.k8s.io/controller-runtime/pkg/controller" 12 | "sigs.k8s.io/controller-runtime/pkg/handler" 13 | "sigs.k8s.io/controller-runtime/pkg/manager" 14 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 15 | "sigs.k8s.io/controller-runtime/pkg/source" 16 | 17 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 18 | ) 19 | 20 | func main() { 21 | scheme := runtime.NewScheme() // ❶ 22 | clientgoscheme.AddToScheme(scheme) 23 | mygroupv1alpha1.AddToScheme(scheme) 24 | 25 | mgr, err := manager.New( // ❷ 26 | config.GetConfigOrDie(), 27 | manager.Options{ 28 | Scheme: scheme, 29 | }, 30 | ) 31 | panicIf(err) 32 | 33 | controller, err := controller.New( // ❸ 34 | "my-operator", mgr, 35 | controller.Options{ 36 | Reconciler: &MyReconciler{}, 37 | }) 38 | panicIf(err) 39 | 40 | err = controller.Watch( // ❹ 41 | &source.Kind{ 42 | Type: &mygroupv1alpha1.MyResource{}, 43 | }, 44 | &handler.EnqueueRequestForObject{}, 45 | ) 46 | panicIf(err) 47 | 48 | err = controller.Watch( // ❺ 49 | &source.Kind{ 50 | Type: &corev1.Pod{}, 51 | }, 52 | &handler.EnqueueRequestForOwner{ 53 | OwnerType: &corev1.Pod{}, 54 | IsController: true, 55 | }, 56 | ) 57 | panicIf(err) 58 | 59 | err = mgr.Start(context.Background()) // ❻ 60 | panicIf(err) 61 | } 62 | 63 | type MyReconciler struct{} // ➐ 64 | 65 | func (o *MyReconciler) Reconcile( // ➑ 66 | ctx context.Context, 67 | r reconcile.Request, 68 | ) (reconcile.Result, error) { 69 | fmt.Printf("reconcile %v\n", r) 70 | return reconcile.Result{}, nil 71 | } 72 | 73 | // panicIf panic if err is not nil 74 | // Please call from main only! 75 | func panicIf(err error) { 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ch10/ex2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch10/ex2 2 | 3 | go 1.19 4 | 5 | replace github.com/myid/myresource-crd => ../../ch09/github.com/myid/myresource-crd 6 | 7 | require ( 8 | github.com/myid/myresource-crd v0.0.0-00010101000000-000000000000 9 | k8s.io/api v0.25.3 10 | k8s.io/apimachinery v0.25.3 11 | k8s.io/client-go v0.25.3 12 | sigs.k8s.io/controller-runtime v0.13.0 13 | ) 14 | 15 | require ( 16 | github.com/PuerkitoBio/purell v1.1.1 // indirect 17 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 23 | github.com/fsnotify/fsnotify v1.5.4 // indirect 24 | github.com/go-logr/logr v1.2.3 // indirect 25 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 26 | github.com/go-openapi/jsonreference v0.19.5 // indirect 27 | github.com/go-openapi/swag v0.19.14 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 30 | github.com/golang/protobuf v1.5.2 // indirect 31 | github.com/google/gnostic v0.5.7-v3refs // indirect 32 | github.com/google/go-cmp v0.5.8 // indirect 33 | github.com/google/gofuzz v1.1.0 // indirect 34 | github.com/google/uuid v1.1.2 // indirect 35 | github.com/imdario/mergo v0.3.12 // indirect 36 | github.com/josharian/intern v1.0.0 // indirect 37 | github.com/json-iterator/go v1.1.12 // indirect 38 | github.com/mailru/easyjson v0.7.6 // indirect 39 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 43 | github.com/pkg/errors v0.9.1 // indirect 44 | github.com/prometheus/client_golang v1.12.2 // indirect 45 | github.com/prometheus/client_model v0.2.0 // indirect 46 | github.com/prometheus/common v0.32.1 // indirect 47 | github.com/prometheus/procfs v0.7.3 // indirect 48 | github.com/spf13/pflag v1.0.5 // indirect 49 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 50 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 51 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 52 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 53 | golang.org/x/text v0.3.7 // indirect 54 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 55 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 56 | google.golang.org/appengine v1.6.7 // indirect 57 | google.golang.org/protobuf v1.28.0 // indirect 58 | gopkg.in/inf.v0 v0.9.1 // indirect 59 | gopkg.in/yaml.v2 v2.4.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | k8s.io/apiextensions-apiserver v0.25.0 // indirect 62 | k8s.io/component-base v0.25.0 // indirect 63 | k8s.io/klog/v2 v2.70.1 // indirect 64 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 65 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 66 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 67 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 68 | sigs.k8s.io/yaml v1.3.0 // indirect 69 | ) 70 | -------------------------------------------------------------------------------- /ch10/ex2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 10 | "sigs.k8s.io/controller-runtime/pkg/builder" 11 | "sigs.k8s.io/controller-runtime/pkg/client/config" 12 | "sigs.k8s.io/controller-runtime/pkg/manager" 13 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 14 | 15 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 16 | ) 17 | 18 | func main() { 19 | 20 | scheme := runtime.NewScheme() 21 | clientgoscheme.AddToScheme(scheme) 22 | mygroupv1alpha1.AddToScheme(scheme) 23 | 24 | mgr, err := manager.New( 25 | config.GetConfigOrDie(), 26 | manager.Options{ 27 | Scheme: scheme, 28 | }, 29 | ) 30 | panicIf(err) 31 | 32 | err = builder. 33 | ControllerManagedBy(mgr). 34 | For(&mygroupv1alpha1.MyResource{}). 35 | Owns(&corev1.Pod{}). 36 | Complete(&MyReconciler{}) 37 | panicIf(err) 38 | 39 | err = mgr.Start(context.Background()) 40 | panicIf(err) 41 | } 42 | 43 | type MyReconciler struct{} 44 | 45 | func (a *MyReconciler) Reconcile( 46 | ctx context.Context, 47 | req reconcile.Request, 48 | ) (reconcile.Result, error) { 49 | fmt.Printf("reconcile %v\n", req) 50 | return reconcile.Result{}, nil 51 | } 52 | 53 | func panicIf(err error) { 54 | if err != nil { 55 | panic(err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ch10/injectors/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch10/injectors 2 | 3 | go 1.19 4 | 5 | replace github.com/myid/myresource-crd => ../../ch09/github.com/myid/myresource-crd 6 | 7 | require ( 8 | github.com/myid/myresource-crd v0.0.0-00010101000000-000000000000 9 | k8s.io/api v0.25.3 10 | k8s.io/apimachinery v0.25.3 11 | k8s.io/client-go v0.25.3 12 | sigs.k8s.io/controller-runtime v0.13.0 13 | ) 14 | 15 | require ( 16 | github.com/PuerkitoBio/purell v1.1.1 // indirect 17 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 23 | github.com/fsnotify/fsnotify v1.5.4 // indirect 24 | github.com/go-logr/logr v1.2.3 // indirect 25 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 26 | github.com/go-openapi/jsonreference v0.19.5 // indirect 27 | github.com/go-openapi/swag v0.19.14 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 30 | github.com/golang/protobuf v1.5.2 // indirect 31 | github.com/google/gnostic v0.5.7-v3refs // indirect 32 | github.com/google/go-cmp v0.5.8 // indirect 33 | github.com/google/gofuzz v1.1.0 // indirect 34 | github.com/google/uuid v1.1.2 // indirect 35 | github.com/imdario/mergo v0.3.12 // indirect 36 | github.com/josharian/intern v1.0.0 // indirect 37 | github.com/json-iterator/go v1.1.12 // indirect 38 | github.com/mailru/easyjson v0.7.6 // indirect 39 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 43 | github.com/pkg/errors v0.9.1 // indirect 44 | github.com/prometheus/client_golang v1.12.2 // indirect 45 | github.com/prometheus/client_model v0.2.0 // indirect 46 | github.com/prometheus/common v0.32.1 // indirect 47 | github.com/prometheus/procfs v0.7.3 // indirect 48 | github.com/spf13/pflag v1.0.5 // indirect 49 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 50 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 51 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 52 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 53 | golang.org/x/text v0.3.7 // indirect 54 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 55 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 56 | google.golang.org/appengine v1.6.7 // indirect 57 | google.golang.org/protobuf v1.28.0 // indirect 58 | gopkg.in/inf.v0 v0.9.1 // indirect 59 | gopkg.in/yaml.v2 v2.4.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | k8s.io/apiextensions-apiserver v0.25.0 // indirect 62 | k8s.io/component-base v0.25.0 // indirect 63 | k8s.io/klog/v2 v2.70.1 // indirect 64 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 65 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 66 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 67 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 68 | sigs.k8s.io/yaml v1.3.0 // indirect 69 | ) 70 | -------------------------------------------------------------------------------- /ch10/injectors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 10 | "sigs.k8s.io/controller-runtime/pkg/builder" 11 | "sigs.k8s.io/controller-runtime/pkg/cache" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | "sigs.k8s.io/controller-runtime/pkg/client/config" 14 | "sigs.k8s.io/controller-runtime/pkg/manager" 15 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 16 | 17 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 18 | ) 19 | 20 | func main() { 21 | 22 | scheme := runtime.NewScheme() 23 | clientgoscheme.AddToScheme(scheme) 24 | mygroupv1alpha1.AddToScheme(scheme) 25 | 26 | mgr, err := manager.New( 27 | config.GetConfigOrDie(), 28 | manager.Options{ 29 | Scheme: scheme, 30 | }, 31 | ) 32 | panicIf(err) 33 | 34 | err = builder. 35 | ControllerManagedBy(mgr). 36 | For(&mygroupv1alpha1.MyResource{}). 37 | Owns(&corev1.Pod{}). 38 | Complete(&MyReconciler{}) 39 | panicIf(err) 40 | 41 | err = mgr.Start(context.Background()) 42 | panicIf(err) 43 | } 44 | 45 | type MyReconciler struct { 46 | client client.Client 47 | cache cache.Cache 48 | scheme *runtime.Scheme 49 | } 50 | 51 | func (a *MyReconciler) Reconcile( 52 | ctx context.Context, 53 | req reconcile.Request, 54 | ) (reconcile.Result, error) { 55 | fmt.Printf("reconcile %v\n", req) 56 | fmt.Printf("client: %p\n", a.client) 57 | fmt.Printf("cache: %p\n", a.cache) 58 | fmt.Printf("scheme: %p\n", a.scheme) 59 | return reconcile.Result{}, nil 60 | } 61 | 62 | func (a *MyReconciler) InjectClient( 63 | c client.Client, 64 | ) error { 65 | a.client = c 66 | return nil 67 | } 68 | 69 | func (a *MyReconciler) InjectCache( 70 | c cache.Cache, 71 | ) error { 72 | a.cache = c 73 | return nil 74 | } 75 | 76 | func (a *MyReconciler) InjectScheme( 77 | s *runtime.Scheme, 78 | ) error { 79 | a.scheme = s 80 | return nil 81 | } 82 | 83 | func panicIf(err error) { 84 | if err != nil { 85 | panic(err) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ch10/logging/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch10/logging 2 | 3 | go 1.19 4 | 5 | replace github.com/myid/myresource-crd => ../../ch09/github.com/myid/myresource-crd 6 | 7 | require ( 8 | github.com/myid/myresource-crd v0.0.0-00010101000000-000000000000 9 | k8s.io/api v0.25.3 10 | k8s.io/apimachinery v0.25.3 11 | k8s.io/client-go v0.25.3 12 | sigs.k8s.io/controller-runtime v0.13.0 13 | ) 14 | 15 | require ( 16 | github.com/PuerkitoBio/purell v1.1.1 // indirect 17 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 23 | github.com/fsnotify/fsnotify v1.5.4 // indirect 24 | github.com/go-logr/logr v1.2.3 // indirect 25 | github.com/go-logr/zapr v1.2.3 // indirect 26 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 27 | github.com/go-openapi/jsonreference v0.19.5 // indirect 28 | github.com/go-openapi/swag v0.19.14 // indirect 29 | github.com/gogo/protobuf v1.3.2 // indirect 30 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 31 | github.com/golang/protobuf v1.5.2 // indirect 32 | github.com/google/gnostic v0.5.7-v3refs // indirect 33 | github.com/google/go-cmp v0.5.8 // indirect 34 | github.com/google/gofuzz v1.1.0 // indirect 35 | github.com/google/uuid v1.1.2 // indirect 36 | github.com/imdario/mergo v0.3.12 // indirect 37 | github.com/josharian/intern v1.0.0 // indirect 38 | github.com/json-iterator/go v1.1.12 // indirect 39 | github.com/mailru/easyjson v0.7.6 // indirect 40 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 42 | github.com/modern-go/reflect2 v1.0.2 // indirect 43 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 44 | github.com/pkg/errors v0.9.1 // indirect 45 | github.com/prometheus/client_golang v1.12.2 // indirect 46 | github.com/prometheus/client_model v0.2.0 // indirect 47 | github.com/prometheus/common v0.32.1 // indirect 48 | github.com/prometheus/procfs v0.7.3 // indirect 49 | github.com/spf13/pflag v1.0.5 // indirect 50 | go.uber.org/atomic v1.7.0 // indirect 51 | go.uber.org/multierr v1.6.0 // indirect 52 | go.uber.org/zap v1.21.0 // indirect 53 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 54 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 55 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 56 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 57 | golang.org/x/text v0.3.7 // indirect 58 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 59 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 60 | google.golang.org/appengine v1.6.7 // indirect 61 | google.golang.org/protobuf v1.28.0 // indirect 62 | gopkg.in/inf.v0 v0.9.1 // indirect 63 | gopkg.in/yaml.v2 v2.4.0 // indirect 64 | gopkg.in/yaml.v3 v3.0.1 // indirect 65 | k8s.io/apiextensions-apiserver v0.25.0 // indirect 66 | k8s.io/component-base v0.25.0 // indirect 67 | k8s.io/klog/v2 v2.70.1 // indirect 68 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 69 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 70 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 71 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 72 | sigs.k8s.io/yaml v1.3.0 // indirect 73 | ) 74 | -------------------------------------------------------------------------------- /ch10/logging/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | "k8s.io/apimachinery/pkg/runtime" 8 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 9 | "sigs.k8s.io/controller-runtime/pkg/builder" 10 | "sigs.k8s.io/controller-runtime/pkg/client/config" 11 | "sigs.k8s.io/controller-runtime/pkg/log" 12 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 13 | "sigs.k8s.io/controller-runtime/pkg/manager" 14 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 15 | 16 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 17 | ) 18 | 19 | func main() { 20 | log.SetLogger(zap.New()) 21 | log.Log.Info("starting") 22 | 23 | scheme := runtime.NewScheme() 24 | clientgoscheme.AddToScheme(scheme) 25 | mygroupv1alpha1.AddToScheme(scheme) 26 | 27 | mgr, err := manager.New( 28 | config.GetConfigOrDie(), 29 | manager.Options{ 30 | Scheme: scheme, 31 | }, 32 | ) 33 | panicIf(err) 34 | 35 | err = builder. 36 | ControllerManagedBy(mgr). 37 | For(&mygroupv1alpha1.MyResource{}). 38 | Owns(&corev1.Pod{}). 39 | Complete(&MyReconciler{}) 40 | panicIf(err) 41 | 42 | err = mgr.Start(context.Background()) 43 | panicIf(err) 44 | } 45 | 46 | type MyReconciler struct{} 47 | 48 | func (a *MyReconciler) Reconcile( 49 | ctx context.Context, 50 | req reconcile.Request, 51 | ) (reconcile.Result, error) { 52 | log := log.FromContext(ctx).WithName("reconcile") 53 | log.Info("reconciling") 54 | return reconcile.Result{}, nil 55 | } 56 | 57 | func panicIf(err error) { 58 | if err != nil { 59 | panic(err) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ch11/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch11 2 | 3 | go 1.19 4 | 5 | replace github.com/myid/myresource-crd => ../ch09/github.com/myid/myresource-crd 6 | 7 | require ( 8 | github.com/myid/myresource-crd v0.0.0-00010101000000-000000000000 9 | k8s.io/api v0.25.3 10 | k8s.io/apimachinery v0.25.3 11 | k8s.io/client-go v0.25.3 12 | sigs.k8s.io/controller-runtime v0.13.0 13 | ) 14 | 15 | require ( 16 | github.com/PuerkitoBio/purell v1.1.1 // indirect 17 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 23 | github.com/fsnotify/fsnotify v1.5.4 // indirect 24 | github.com/go-logr/logr v1.2.3 // indirect 25 | github.com/go-logr/zapr v1.2.3 // indirect 26 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 27 | github.com/go-openapi/jsonreference v0.19.5 // indirect 28 | github.com/go-openapi/swag v0.19.14 // indirect 29 | github.com/gogo/protobuf v1.3.2 // indirect 30 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 31 | github.com/golang/protobuf v1.5.2 // indirect 32 | github.com/google/gnostic v0.5.7-v3refs // indirect 33 | github.com/google/go-cmp v0.5.8 // indirect 34 | github.com/google/gofuzz v1.1.0 // indirect 35 | github.com/google/uuid v1.1.2 // indirect 36 | github.com/imdario/mergo v0.3.12 // indirect 37 | github.com/josharian/intern v1.0.0 // indirect 38 | github.com/json-iterator/go v1.1.12 // indirect 39 | github.com/mailru/easyjson v0.7.6 // indirect 40 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 42 | github.com/modern-go/reflect2 v1.0.2 // indirect 43 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 44 | github.com/pkg/errors v0.9.1 // indirect 45 | github.com/prometheus/client_golang v1.12.2 // indirect 46 | github.com/prometheus/client_model v0.2.0 // indirect 47 | github.com/prometheus/common v0.32.1 // indirect 48 | github.com/prometheus/procfs v0.7.3 // indirect 49 | github.com/spf13/pflag v1.0.5 // indirect 50 | go.uber.org/atomic v1.7.0 // indirect 51 | go.uber.org/multierr v1.6.0 // indirect 52 | go.uber.org/zap v1.21.0 // indirect 53 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 54 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 55 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 56 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 57 | golang.org/x/text v0.3.7 // indirect 58 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 59 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 60 | google.golang.org/appengine v1.6.7 // indirect 61 | google.golang.org/protobuf v1.28.0 // indirect 62 | gopkg.in/inf.v0 v0.9.1 // indirect 63 | gopkg.in/yaml.v2 v2.4.0 // indirect 64 | gopkg.in/yaml.v3 v3.0.1 // indirect 65 | k8s.io/apiextensions-apiserver v0.25.0 // indirect 66 | k8s.io/component-base v0.25.0 // indirect 67 | k8s.io/klog/v2 v2.70.1 // indirect 68 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 69 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 70 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 71 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 72 | sigs.k8s.io/yaml v1.3.0 // indirect 73 | ) 74 | -------------------------------------------------------------------------------- /ch11/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | appsv1 "k8s.io/api/apps/v1" 7 | "k8s.io/apimachinery/pkg/runtime" 8 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 9 | "sigs.k8s.io/controller-runtime/pkg/builder" 10 | "sigs.k8s.io/controller-runtime/pkg/client/config" 11 | "sigs.k8s.io/controller-runtime/pkg/log" 12 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 13 | "sigs.k8s.io/controller-runtime/pkg/manager" 14 | 15 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 16 | ) 17 | 18 | func main() { 19 | log.SetLogger(zap.New()) 20 | 21 | scheme := runtime.NewScheme() 22 | clientgoscheme.AddToScheme(scheme) 23 | mygroupv1alpha1.AddToScheme(scheme) 24 | 25 | mgr, err := manager.New( 26 | config.GetConfigOrDie(), 27 | manager.Options{ 28 | Scheme: scheme, 29 | }, 30 | ) 31 | panicIf(err) 32 | 33 | eventRecorder := mgr.GetEventRecorderFor( 34 | "MyResource", 35 | ) 36 | 37 | err = builder. 38 | ControllerManagedBy(mgr). 39 | For(&mygroupv1alpha1.MyResource{}). 40 | Owns(&appsv1.Deployment{}). 41 | Complete(&MyReconciler{ 42 | EventRecorder: eventRecorder, 43 | }) 44 | panicIf(err) 45 | 46 | err = mgr.Start(context.Background()) 47 | panicIf(err) 48 | } 49 | 50 | func panicIf(err error) { 51 | if err != nil { 52 | panic(err) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ch11/reconciler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | appsv1 "k8s.io/api/apps/v1" 8 | corev1 "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/client-go/tools/record" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | "sigs.k8s.io/controller-runtime/pkg/log" 14 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 15 | 16 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 17 | ) 18 | 19 | const ( 20 | Name = "MyResourceReconciler" 21 | 22 | _buildingState = "Building" 23 | _readyState = "Ready" 24 | ) 25 | 26 | type MyReconciler struct { 27 | client client.Client 28 | EventRecorder record.EventRecorder 29 | } 30 | 31 | func (a *MyReconciler) InjectClient( 32 | c client.Client, 33 | ) error { 34 | a.client = c 35 | return nil 36 | } 37 | 38 | func (a *MyReconciler) Reconcile( 39 | ctx context.Context, 40 | req reconcile.Request, 41 | ) (reconcile.Result, error) { 42 | log := log.FromContext(ctx) 43 | 44 | log.Info("getting myresource instance") 45 | 46 | myresource := mygroupv1alpha1.MyResource{} 47 | err := a.client.Get( // ❶ 48 | ctx, 49 | req.NamespacedName, 50 | &myresource, 51 | &client.GetOptions{}, 52 | ) 53 | if err != nil { 54 | if errors.IsNotFound(err) { // ❷ 55 | log.Info("resource is not found") 56 | return reconcile.Result{}, nil 57 | } 58 | return reconcile.Result{}, err 59 | } 60 | 61 | ownerReference := metav1.NewControllerRef( // ❸ 62 | &myresource, 63 | mygroupv1alpha1.SchemeGroupVersion. 64 | WithKind("MyResource"), 65 | ) 66 | 67 | err = a.applyDeployment( // ❹ 68 | ctx, 69 | &myresource, 70 | ownerReference, 71 | ) 72 | if err != nil { 73 | return reconcile.Result{}, err 74 | } 75 | 76 | status, err := a.computeStatus(ctx, &myresource) // ❺ 77 | if err != nil { 78 | return reconcile.Result{}, err 79 | } 80 | myresource.Status = *status 81 | log.Info("updating status", "state", status.State) 82 | err = a.client.Status().Update(ctx, &myresource) // ❻ 83 | if err != nil { 84 | return reconcile.Result{}, err 85 | } 86 | 87 | return reconcile.Result{}, nil 88 | } 89 | 90 | func (a *MyReconciler) applyDeployment( 91 | ctx context.Context, 92 | myres *mygroupv1alpha1.MyResource, 93 | ownerref *metav1.OwnerReference, 94 | ) error { 95 | deploy := createDeployment(myres, ownerref) 96 | err := a.client.Patch( // ❼ 97 | ctx, 98 | deploy, 99 | client.Apply, 100 | client.FieldOwner(Name), 101 | client.ForceOwnership, 102 | ) 103 | return err 104 | } 105 | 106 | func createDeployment( 107 | myres *mygroupv1alpha1.MyResource, 108 | ownerref *metav1.OwnerReference, 109 | ) *appsv1.Deployment { 110 | deploy := &appsv1.Deployment{ 111 | ObjectMeta: metav1.ObjectMeta{ 112 | Labels: map[string]string{ 113 | "myresource": myres.GetName(), 114 | }, 115 | }, 116 | Spec: appsv1.DeploymentSpec{ 117 | Selector: &metav1.LabelSelector{ 118 | MatchLabels: map[string]string{ 119 | "myresource": myres.GetName(), 120 | }, 121 | }, 122 | Template: corev1.PodTemplateSpec{ 123 | ObjectMeta: metav1.ObjectMeta{ 124 | Labels: map[string]string{ 125 | "myresource": myres.GetName(), 126 | }, 127 | }, 128 | Spec: corev1.PodSpec{ 129 | Containers: []corev1.Container{ 130 | { 131 | Name: "main", 132 | Image: myres.Spec.Image, // ❽ 133 | Resources: corev1.ResourceRequirements{ 134 | Requests: corev1.ResourceList{ 135 | corev1.ResourceMemory: myres.Spec.Memory, // ❾ 136 | }, 137 | }, 138 | }, 139 | }, 140 | }, 141 | }, 142 | }, 143 | } 144 | deploy.SetName(myres.GetName() + "-deployment") 145 | deploy.SetNamespace(myres.GetNamespace()) 146 | deploy.SetGroupVersionKind( 147 | appsv1.SchemeGroupVersion.WithKind("Deployment"), 148 | ) 149 | deploy.SetOwnerReferences([]metav1.OwnerReference{ // ❿ 150 | *ownerref, 151 | }) 152 | return deploy 153 | } 154 | 155 | func (a *MyReconciler) computeStatus( 156 | ctx context.Context, 157 | myres *mygroupv1alpha1.MyResource, 158 | ) (*mygroupv1alpha1.MyResourceStatus, error) { 159 | 160 | logger := log.FromContext(ctx) 161 | result := mygroupv1alpha1.MyResourceStatus{ 162 | State: _buildingState, 163 | } 164 | 165 | deployList := appsv1.DeploymentList{} 166 | err := a.client.List( // ⓫ 167 | ctx, 168 | &deployList, 169 | client.InNamespace(myres.GetNamespace()), 170 | client.MatchingLabels{ 171 | "myresource": myres.GetName(), 172 | }, 173 | ) 174 | if err != nil { 175 | return nil, err 176 | } 177 | 178 | if len(deployList.Items) == 0 { 179 | logger.Info("no deployment found") 180 | return &result, nil 181 | } 182 | 183 | if len(deployList.Items) > 1 { 184 | logger.Info( 185 | "too many deployments found", "count", 186 | len(deployList.Items), 187 | ) 188 | return nil, fmt.Errorf( 189 | "%d deployment found, expected 1", 190 | len(deployList.Items), 191 | ) 192 | } 193 | 194 | status := deployList.Items[0].Status // ⓬ 195 | logger.Info( 196 | "got deployment status", 197 | "status", status, 198 | ) 199 | if status.ReadyReplicas == 1 { 200 | result.State = _readyState // ⓭ 201 | } 202 | 203 | return &result, nil 204 | } 205 | -------------------------------------------------------------------------------- /ch12/crd/crd-with-cols.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: myresources.mygroup.example.com 5 | spec: 6 | group: mygroup.example.com 7 | scope: Namespaced 8 | names: 9 | plural: myresources 10 | singular: myresource 11 | shortNames: 12 | - my 13 | - myres 14 | kind: MyResource 15 | categories: 16 | - all 17 | versions: 18 | - name: v1alpha1 19 | served: true 20 | storage: true 21 | subresources: 22 | status: {} 23 | schema: 24 | openAPIV3Schema: 25 | type: object 26 | properties: 27 | spec: 28 | type: object 29 | properties: 30 | image: 31 | type: string 32 | memory: 33 | x-kubernetes-int-or-string: true 34 | status: 35 | type: object 36 | properties: 37 | state: 38 | type: string 39 | additionalPrinterColumns: 40 | - name: image 41 | jsonPath: .spec.image 42 | type: string 43 | - name: memory 44 | jsonPath: .spec.memory 45 | type: string 46 | - name: age 47 | jsonPath: .metadata.creationTimestamp 48 | type: date 49 | 50 | -------------------------------------------------------------------------------- /ch12/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kprogo/ch12 2 | 3 | go 1.19 4 | 5 | replace github.com/myid/myresource-crd => ../ch09/github.com/myid/myresource-crd 6 | 7 | require ( 8 | github.com/myid/myresource-crd v0.0.0-00010101000000-000000000000 9 | github.com/onsi/ginkgo/v2 v2.1.6 10 | github.com/onsi/gomega v1.20.1 11 | k8s.io/api v0.25.3 12 | k8s.io/apimachinery v0.25.3 13 | k8s.io/client-go v0.25.3 14 | sigs.k8s.io/controller-runtime v0.13.0 15 | ) 16 | 17 | require ( 18 | github.com/PuerkitoBio/purell v1.1.1 // indirect 19 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 20 | github.com/beorn7/perks v1.0.1 // indirect 21 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 22 | github.com/davecgh/go-spew v1.1.1 // indirect 23 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 24 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 25 | github.com/fsnotify/fsnotify v1.5.4 // indirect 26 | github.com/go-logr/logr v1.2.3 // indirect 27 | github.com/go-logr/zapr v1.2.3 // indirect 28 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 29 | github.com/go-openapi/jsonreference v0.19.5 // indirect 30 | github.com/go-openapi/swag v0.19.14 // indirect 31 | github.com/gogo/protobuf v1.3.2 // indirect 32 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 33 | github.com/golang/protobuf v1.5.2 // indirect 34 | github.com/google/gnostic v0.5.7-v3refs // indirect 35 | github.com/google/go-cmp v0.5.8 // indirect 36 | github.com/google/gofuzz v1.1.0 // indirect 37 | github.com/google/uuid v1.1.2 // indirect 38 | github.com/imdario/mergo v0.3.12 // indirect 39 | github.com/josharian/intern v1.0.0 // indirect 40 | github.com/json-iterator/go v1.1.12 // indirect 41 | github.com/mailru/easyjson v0.7.6 // indirect 42 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 44 | github.com/modern-go/reflect2 v1.0.2 // indirect 45 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 46 | github.com/pkg/errors v0.9.1 // indirect 47 | github.com/prometheus/client_golang v1.12.2 // indirect 48 | github.com/prometheus/client_model v0.2.0 // indirect 49 | github.com/prometheus/common v0.32.1 // indirect 50 | github.com/prometheus/procfs v0.7.3 // indirect 51 | github.com/spf13/pflag v1.0.5 // indirect 52 | go.uber.org/atomic v1.7.0 // indirect 53 | go.uber.org/multierr v1.6.0 // indirect 54 | go.uber.org/zap v1.21.0 // indirect 55 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 56 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 57 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 58 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 59 | golang.org/x/text v0.3.7 // indirect 60 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 61 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 62 | google.golang.org/appengine v1.6.7 // indirect 63 | google.golang.org/protobuf v1.28.0 // indirect 64 | gopkg.in/inf.v0 v0.9.1 // indirect 65 | gopkg.in/yaml.v2 v2.4.0 // indirect 66 | gopkg.in/yaml.v3 v3.0.1 // indirect 67 | k8s.io/apiextensions-apiserver v0.25.0 // indirect 68 | k8s.io/component-base v0.25.0 // indirect 69 | k8s.io/klog/v2 v2.70.1 // indirect 70 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 71 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 72 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 73 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 74 | sigs.k8s.io/yaml v1.3.0 // indirect 75 | ) 76 | -------------------------------------------------------------------------------- /ch12/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | appsv1 "k8s.io/api/apps/v1" 7 | "k8s.io/apimachinery/pkg/runtime" 8 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 9 | "sigs.k8s.io/controller-runtime/pkg/builder" 10 | "sigs.k8s.io/controller-runtime/pkg/client/config" 11 | "sigs.k8s.io/controller-runtime/pkg/log" 12 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 13 | "sigs.k8s.io/controller-runtime/pkg/manager" 14 | 15 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 16 | ) 17 | 18 | func main() { 19 | log.SetLogger(zap.New()) 20 | 21 | scheme := runtime.NewScheme() 22 | clientgoscheme.AddToScheme(scheme) 23 | mygroupv1alpha1.AddToScheme(scheme) 24 | 25 | mgr, err := manager.New( 26 | config.GetConfigOrDie(), 27 | manager.Options{ 28 | Scheme: scheme, 29 | }, 30 | ) 31 | panicIf(err) 32 | 33 | eventRecorder := mgr.GetEventRecorderFor( 34 | "MyResource", 35 | ) 36 | 37 | err = builder. 38 | ControllerManagedBy(mgr). 39 | For(&mygroupv1alpha1.MyResource{}). 40 | Owns(&appsv1.Deployment{}). 41 | Complete(&MyReconciler{ 42 | EventRecorder: eventRecorder, 43 | }) 44 | panicIf(err) 45 | 46 | err = mgr.Start(context.Background()) 47 | panicIf(err) 48 | } 49 | 50 | func panicIf(err error) { 51 | if err != nil { 52 | panic(err) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ch12/reconcile_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | 10 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 11 | 12 | appsv1 "k8s.io/api/apps/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/types" 15 | "sigs.k8s.io/controller-runtime/pkg/client" 16 | ) 17 | 18 | var _ = Describe("MyResource controller", func() { 19 | When("When creating a MyResource instance", func() { 20 | var ( 21 | myres mygroupv1alpha1.MyResource 22 | ownerref *metav1.OwnerReference 23 | name string 24 | namespace = "default" 25 | deployName string 26 | image string 27 | ) 28 | 29 | BeforeEach(func() { 30 | // Create the MyResource instance 31 | image = fmt.Sprintf("myimage%d", rand.Intn(1000)) 32 | myres = mygroupv1alpha1.MyResource{ 33 | Spec: mygroupv1alpha1.MyResourceSpec{ 34 | Image: image, 35 | }, 36 | } 37 | name = fmt.Sprintf("myres%d", rand.Intn(1000)) 38 | myres.SetName(name) 39 | myres.SetNamespace(namespace) 40 | err := k8sClient.Create(ctx, &myres) 41 | Expect(err).NotTo(HaveOccurred()) 42 | ownerref = metav1.NewControllerRef( 43 | &myres, 44 | mygroupv1alpha1.SchemeGroupVersion. 45 | WithKind("MyResource"), 46 | ) 47 | deployName = fmt.Sprintf("%s-deployment", name) 48 | }) 49 | 50 | AfterEach(func() { 51 | // Delete the MyResource instance 52 | k8sClient.Delete(ctx, &myres) 53 | }) 54 | 55 | It("should create a deployment", func() { 56 | // Check that the deployment 57 | // is eventually created 58 | var dep appsv1.Deployment 59 | Eventually( 60 | deploymentExists(deployName, namespace, &dep), 61 | 10, 1, 62 | ).Should(BeTrue()) 63 | }) 64 | 65 | When("deployment is found", func() { 66 | var dep appsv1.Deployment 67 | BeforeEach(func() { 68 | // Wait for the deployment 69 | // to be eventually created 70 | Eventually( 71 | deploymentExists(deployName, namespace, &dep), 72 | 10, 1, 73 | ).Should(BeTrue()) 74 | }) 75 | 76 | It("should be owned by the MyResource instance", func() { 77 | // Check ownerReference in Deployment 78 | // references the MyResource instance 79 | Expect(dep.GetOwnerReferences()). 80 | To(ContainElement(*ownerref)) 81 | }) 82 | 83 | It("should use the image specified in MyResource instance", func() { 84 | Expect( 85 | dep.Spec.Template.Spec.Containers[0].Image, 86 | ).To(Equal(image)) 87 | }) 88 | 89 | When("deployment ReadyReplicas is 1", func() { 90 | BeforeEach(func() { 91 | // Update the Deployment status 92 | // to ReadyReplicas=1 93 | dep.Status.Replicas = 1 94 | dep.Status.ReadyReplicas = 1 95 | err := k8sClient.Status().Update(ctx, &dep) 96 | Expect(err).NotTo(HaveOccurred()) 97 | }) 98 | 99 | It("should set status ready for MyResource instance", func() { 100 | // Check the status of MyResource instance 101 | // is eventually Ready 102 | Eventually( 103 | getMyResourceState(name, namespace), 10, 1, 104 | ).Should(Equal("Ready")) 105 | }) 106 | }) 107 | }) 108 | }) 109 | }) 110 | 111 | func deploymentExists( 112 | name, namespace string, dep *appsv1.Deployment, 113 | ) func() bool { 114 | return func() bool { 115 | err := k8sClient.Get(ctx, client.ObjectKey{ 116 | Namespace: namespace, 117 | Name: name, 118 | }, dep) 119 | return err == nil 120 | } 121 | } 122 | 123 | func getMyResourceState( 124 | name, namespace string, 125 | ) func() (string, error) { 126 | return func() (string, error) { 127 | myres := mygroupv1alpha1.MyResource{} 128 | err := k8sClient.Get(ctx, types.NamespacedName{ 129 | Namespace: namespace, 130 | Name: name, 131 | }, &myres) 132 | if err != nil { 133 | return "", err 134 | } 135 | return myres.Status.State, nil 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /ch12/reconciler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | appsv1 "k8s.io/api/apps/v1" 8 | corev1 "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/client-go/tools/record" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | "sigs.k8s.io/controller-runtime/pkg/log" 14 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 15 | 16 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 17 | ) 18 | 19 | const ( 20 | Name = "MyResourceReconciler" 21 | 22 | _buildingState = "Building" 23 | _readyState = "Ready" 24 | ) 25 | 26 | type MyReconciler struct { 27 | client client.Client 28 | EventRecorder record.EventRecorder 29 | } 30 | 31 | func (a *MyReconciler) InjectClient( 32 | c client.Client, 33 | ) error { 34 | a.client = c 35 | return nil 36 | } 37 | 38 | func (a *MyReconciler) Reconcile( 39 | ctx context.Context, 40 | req reconcile.Request, 41 | ) (reconcile.Result, error) { 42 | log := log.FromContext(ctx) 43 | 44 | log.Info("getting myresource instance") 45 | 46 | myresource := mygroupv1alpha1.MyResource{} 47 | err := a.client.Get( // ❶ 48 | ctx, 49 | req.NamespacedName, 50 | &myresource, 51 | &client.GetOptions{}, 52 | ) 53 | if err != nil { 54 | if errors.IsNotFound(err) { // ❷ 55 | log.Info("resource is not found") 56 | return reconcile.Result{}, nil 57 | } 58 | return reconcile.Result{}, err 59 | } 60 | 61 | ownerReference := metav1.NewControllerRef( // ❸ 62 | &myresource, 63 | mygroupv1alpha1.SchemeGroupVersion. 64 | WithKind("MyResource"), 65 | ) 66 | 67 | err = a.applyDeployment( // ❹ 68 | ctx, 69 | &myresource, 70 | ownerReference, 71 | ) 72 | if err != nil { 73 | return reconcile.Result{}, err 74 | } 75 | 76 | status, err := a.computeStatus(ctx, &myresource) // ❺ 77 | if err != nil { 78 | return reconcile.Result{}, err 79 | } 80 | myresource.Status = *status 81 | log.Info("updating status", "state", status.State) 82 | err = a.client.Status().Update(ctx, &myresource) // ❻ 83 | if err != nil { 84 | return reconcile.Result{}, err 85 | } 86 | 87 | return reconcile.Result{}, nil 88 | } 89 | 90 | func (a *MyReconciler) applyDeployment( 91 | ctx context.Context, 92 | myres *mygroupv1alpha1.MyResource, 93 | ownerref *metav1.OwnerReference, 94 | ) error { 95 | deploy := createDeployment(myres, ownerref) 96 | err := a.client.Patch( // ❼ 97 | ctx, 98 | deploy, 99 | client.Apply, 100 | client.FieldOwner(Name), 101 | client.ForceOwnership, 102 | ) 103 | return err 104 | } 105 | 106 | func createDeployment( 107 | myres *mygroupv1alpha1.MyResource, 108 | ownerref *metav1.OwnerReference, 109 | ) *appsv1.Deployment { 110 | deploy := &appsv1.Deployment{ 111 | ObjectMeta: metav1.ObjectMeta{ 112 | Labels: map[string]string{ 113 | "myresource": myres.GetName(), 114 | }, 115 | }, 116 | Spec: appsv1.DeploymentSpec{ 117 | Selector: &metav1.LabelSelector{ 118 | MatchLabels: map[string]string{ 119 | "myresource": myres.GetName(), 120 | }, 121 | }, 122 | Template: corev1.PodTemplateSpec{ 123 | ObjectMeta: metav1.ObjectMeta{ 124 | Labels: map[string]string{ 125 | "myresource": myres.GetName(), 126 | }, 127 | }, 128 | Spec: corev1.PodSpec{ 129 | Containers: []corev1.Container{ 130 | { 131 | Name: "main", 132 | Image: myres.Spec.Image, // ❽ 133 | Resources: corev1.ResourceRequirements{ 134 | Requests: corev1.ResourceList{ 135 | corev1.ResourceMemory: myres.Spec.Memory, // ❾ 136 | }, 137 | }, 138 | }, 139 | }, 140 | }, 141 | }, 142 | }, 143 | } 144 | deploy.SetName(myres.GetName() + "-deployment") 145 | deploy.SetNamespace(myres.GetNamespace()) 146 | deploy.SetGroupVersionKind( 147 | appsv1.SchemeGroupVersion.WithKind("Deployment"), 148 | ) 149 | deploy.SetOwnerReferences([]metav1.OwnerReference{ // ❿ 150 | *ownerref, 151 | }) 152 | return deploy 153 | } 154 | 155 | func (a *MyReconciler) computeStatus( 156 | ctx context.Context, 157 | myres *mygroupv1alpha1.MyResource, 158 | ) (*mygroupv1alpha1.MyResourceStatus, error) { 159 | 160 | logger := log.FromContext(ctx) 161 | result := mygroupv1alpha1.MyResourceStatus{ 162 | State: _buildingState, 163 | } 164 | 165 | deployList := appsv1.DeploymentList{} 166 | err := a.client.List( // ⓫ 167 | ctx, 168 | &deployList, 169 | client.InNamespace(myres.GetNamespace()), 170 | client.MatchingLabels{ 171 | "myresource": myres.GetName(), 172 | }, 173 | ) 174 | if err != nil { 175 | return nil, err 176 | } 177 | 178 | if len(deployList.Items) == 0 { 179 | logger.Info("no deployment found") 180 | return &result, nil 181 | } 182 | 183 | if len(deployList.Items) > 1 { 184 | logger.Info( 185 | "too many deployments found", "count", 186 | len(deployList.Items), 187 | ) 188 | return nil, fmt.Errorf( 189 | "%d deployment found, expected 1", 190 | len(deployList.Items), 191 | ) 192 | } 193 | 194 | status := deployList.Items[0].Status // ⓬ 195 | logger.Info( 196 | "got deployment status", 197 | "status", status, 198 | ) 199 | if status.ReadyReplicas == 1 { 200 | result.State = _readyState // ⓭ 201 | } 202 | 203 | return &result, nil 204 | } 205 | -------------------------------------------------------------------------------- /ch12/suite_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "path/filepath" 6 | "testing" 7 | 8 | mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | 13 | appsv1 "k8s.io/api/apps/v1" 14 | "k8s.io/apimachinery/pkg/runtime" 15 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 16 | "sigs.k8s.io/controller-runtime/pkg/builder" 17 | "sigs.k8s.io/controller-runtime/pkg/client" 18 | "sigs.k8s.io/controller-runtime/pkg/envtest" 19 | "sigs.k8s.io/controller-runtime/pkg/log" 20 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 21 | "sigs.k8s.io/controller-runtime/pkg/manager" 22 | ) 23 | 24 | func TestMyReconciler_Reconcile(t *testing.T) { 25 | RegisterFailHandler(Fail) 26 | RunSpecs(t, 27 | "Controller Suite", 28 | ) 29 | } 30 | 31 | var ( 32 | testEnv *envtest.Environment // ❶ 33 | ctx context.Context 34 | cancel context.CancelFunc 35 | k8sClient client.Client // ❷ 36 | ) 37 | 38 | var _ = BeforeSuite(func() { 39 | log.SetLogger(zap.New( 40 | zap.WriteTo(GinkgoWriter), 41 | zap.UseDevMode(true), 42 | )) 43 | 44 | ctx, cancel = context.WithCancel( // ❸ 45 | context.Background(), 46 | ) 47 | 48 | testEnv = &envtest.Environment{ // ❹ 49 | CRDDirectoryPaths: []string{ 50 | filepath.Join("crd"), 51 | }, 52 | ErrorIfCRDPathMissing: true, 53 | } 54 | 55 | var err error 56 | // cfg is defined in this file globally. 57 | cfg, err := testEnv.Start() // ❺ 58 | Expect(err).NotTo(HaveOccurred()) 59 | Expect(cfg).NotTo(BeNil()) 60 | 61 | scheme := runtime.NewScheme() // ❻ 62 | err = clientgoscheme.AddToScheme(scheme) 63 | Expect(err).NotTo(HaveOccurred()) 64 | err = mygroupv1alpha1.AddToScheme(scheme) 65 | Expect(err).NotTo(HaveOccurred()) 66 | 67 | mgr, err := manager.New(cfg, manager.Options{ // ❼ 68 | Scheme: scheme, 69 | }) 70 | Expect(err).ToNot(HaveOccurred()) 71 | k8sClient = mgr.GetClient() // ❽ 72 | 73 | err = builder. // ❾ 74 | ControllerManagedBy(mgr). 75 | Named(Name). 76 | For(&mygroupv1alpha1.MyResource{}). 77 | Owns(&appsv1.Deployment{}). 78 | Complete(&MyReconciler{}) 79 | 80 | go func() { 81 | defer GinkgoRecover() 82 | err = mgr.Start(ctx) // ❿ 83 | Expect(err).ToNot( 84 | HaveOccurred(), 85 | "failed to run manager", 86 | ) 87 | }() 88 | }) 89 | 90 | var _ = AfterSuite(func() { 91 | cancel() // ⓫ 92 | err := testEnv.Stop() // ⓬ 93 | Expect(err).NotTo(HaveOccurred()) 94 | }) 95 | -------------------------------------------------------------------------------- /ch13/myresource-kb/.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /ch13/myresource-kb/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | Dockerfile.cross 11 | 12 | # Test binary, build with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Kubernetes Generated files - skip generated files, except for vendored files 19 | 20 | !vendor/**/zz_generated.* 21 | 22 | # editor and IDE paraphernalia 23 | .idea 24 | *.swp 25 | *.swo 26 | *~ 27 | -------------------------------------------------------------------------------- /ch13/myresource-kb/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.19 as builder 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | WORKDIR /workspace 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | # cache deps before building and copying source so that we don't need to re-download as much 11 | # and so that source changes don't invalidate our downloaded layer 12 | RUN go mod download 13 | 14 | # Copy the go source 15 | COPY main.go main.go 16 | COPY api/ api/ 17 | COPY controllers/ controllers/ 18 | 19 | # Build 20 | # the GOARCH has not a default value to allow the binary be built according to the host where the command 21 | # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 22 | # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 23 | # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 24 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go 25 | 26 | # Use distroless as minimal base image to package the manager binary 27 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 28 | FROM gcr.io/distroless/static:nonroot 29 | WORKDIR / 30 | COPY --from=builder /workspace/manager . 31 | USER 65532:65532 32 | 33 | ENTRYPOINT ["/manager"] 34 | -------------------------------------------------------------------------------- /ch13/myresource-kb/PROJECT: -------------------------------------------------------------------------------- 1 | domain: myid.dev 2 | layout: 3 | - go.kubebuilder.io/v3 4 | projectName: myresource-kb 5 | repo: github.com/myid/myresource 6 | resources: 7 | - api: 8 | crdVersion: v1 9 | namespaced: true 10 | controller: true 11 | domain: myid.dev 12 | group: mygroup 13 | kind: MyResource 14 | path: github.com/myid/myresource/api/v1alpha1 15 | version: v1alpha1 16 | - api: 17 | crdVersion: v1 18 | namespaced: true 19 | domain: myid.dev 20 | group: mygroup 21 | kind: MyResource 22 | path: github.com/myid/myresource/api/v1beta1 23 | version: v1beta1 24 | webhooks: 25 | conversion: true 26 | webhookVersion: v1 27 | version: "3" 28 | -------------------------------------------------------------------------------- /ch13/myresource-kb/README.md: -------------------------------------------------------------------------------- 1 | # myresource-kb 2 | // TODO(user): Add simple overview of use/purpose 3 | 4 | ## Description 5 | // TODO(user): An in-depth paragraph about your project and overview of use 6 | 7 | ## Getting Started 8 | You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster. 9 | **Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster `kubectl cluster-info` shows). 10 | 11 | ### Running on the cluster 12 | 1. Install Instances of Custom Resources: 13 | 14 | ```sh 15 | kubectl apply -f config/samples/ 16 | ``` 17 | 18 | 2. Build and push your image to the location specified by `IMG`: 19 | 20 | ```sh 21 | make docker-build docker-push IMG=/myresource-kb:tag 22 | ``` 23 | 24 | 3. Deploy the controller to the cluster with the image specified by `IMG`: 25 | 26 | ```sh 27 | make deploy IMG=/myresource-kb:tag 28 | ``` 29 | 30 | ### Uninstall CRDs 31 | To delete the CRDs from the cluster: 32 | 33 | ```sh 34 | make uninstall 35 | ``` 36 | 37 | ### Undeploy controller 38 | UnDeploy the controller to the cluster: 39 | 40 | ```sh 41 | make undeploy 42 | ``` 43 | 44 | ## Contributing 45 | // TODO(user): Add detailed information on how you would like others to contribute to this project 46 | 47 | ### How it works 48 | This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) 49 | 50 | It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/) 51 | which provides a reconcile function responsible for synchronizing resources untile the desired state is reached on the cluster 52 | 53 | ### Test It Out 54 | 1. Install the CRDs into the cluster: 55 | 56 | ```sh 57 | make install 58 | ``` 59 | 60 | 2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running): 61 | 62 | ```sh 63 | make run 64 | ``` 65 | 66 | **NOTE:** You can also run this in one step by running: `make install run` 67 | 68 | ### Modifying the API definitions 69 | If you are editing the API definitions, generate the manifests such as CRs or CRDs using: 70 | 71 | ```sh 72 | make manifests 73 | ``` 74 | 75 | **NOTE:** Run `make --help` for more information on all potential `make` targets 76 | 77 | More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html) 78 | 79 | ## License 80 | 81 | Copyright 2022. 82 | 83 | Licensed under the Apache License, Version 2.0 (the "License"); 84 | you may not use this file except in compliance with the License. 85 | You may obtain a copy of the License at 86 | 87 | http://www.apache.org/licenses/LICENSE-2.0 88 | 89 | Unless required by applicable law or agreed to in writing, software 90 | distributed under the License is distributed on an "AS IS" BASIS, 91 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 92 | See the License for the specific language governing permissions and 93 | limitations under the License. 94 | 95 | -------------------------------------------------------------------------------- /ch13/myresource-kb/api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 v1alpha1 contains API Schema definitions for the mygroup v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=mygroup.myid.dev 20 | package v1alpha1 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.myid.dev", Version: "v1alpha1"} 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 | -------------------------------------------------------------------------------- /ch13/myresource-kb/api/v1alpha1/hub.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | // Hub marks this type as a conversion hub. 4 | func (*MyResource) Hub() {} 5 | -------------------------------------------------------------------------------- /ch13/myresource-kb/api/v1alpha1/myresource_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 v1alpha1 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/api/resource" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 25 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 26 | 27 | // MyResourceSpec defines the desired state of MyResource 28 | type MyResourceSpec struct { 29 | Image string `json:"image"` 30 | Memory resource.Quantity `json:"memory"` 31 | } 32 | 33 | // MyResourceStatus defines the observed state of MyResource 34 | type MyResourceStatus struct { 35 | State string `json:"state"` 36 | } 37 | 38 | //+kubebuilder:object:root=true 39 | //+kubebuilder:subresource:status 40 | //+kubebuilder:printcolumn:name="Image",type=string,JSONPath=`.spec.image` 41 | //+kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state` 42 | //+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 43 | //+kubebuilder:storageversion 44 | 45 | // MyResource is the Schema for the myresources API 46 | type MyResource struct { 47 | metav1.TypeMeta `json:",inline"` 48 | metav1.ObjectMeta `json:"metadata,omitempty"` 49 | 50 | Spec MyResourceSpec `json:"spec,omitempty"` 51 | Status MyResourceStatus `json:"status,omitempty"` 52 | } 53 | 54 | //+kubebuilder:object:root=true 55 | 56 | // MyResourceList contains a list of MyResource 57 | type MyResourceList struct { 58 | metav1.TypeMeta `json:",inline"` 59 | metav1.ListMeta `json:"metadata,omitempty"` 60 | Items []MyResource `json:"items"` 61 | } 62 | 63 | func init() { 64 | SchemeBuilder.Register(&MyResource{}, &MyResourceList{}) 65 | } 66 | -------------------------------------------------------------------------------- /ch13/myresource-kb/api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2022. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *MyResource) DeepCopyInto(out *MyResource) { 30 | *out = *in 31 | out.TypeMeta = in.TypeMeta 32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 33 | in.Spec.DeepCopyInto(&out.Spec) 34 | out.Status = in.Status 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResource. 38 | func (in *MyResource) DeepCopy() *MyResource { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(MyResource) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 48 | func (in *MyResource) DeepCopyObject() runtime.Object { 49 | if c := in.DeepCopy(); c != nil { 50 | return c 51 | } 52 | return nil 53 | } 54 | 55 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 56 | func (in *MyResourceList) DeepCopyInto(out *MyResourceList) { 57 | *out = *in 58 | out.TypeMeta = in.TypeMeta 59 | in.ListMeta.DeepCopyInto(&out.ListMeta) 60 | if in.Items != nil { 61 | in, out := &in.Items, &out.Items 62 | *out = make([]MyResource, len(*in)) 63 | for i := range *in { 64 | (*in)[i].DeepCopyInto(&(*out)[i]) 65 | } 66 | } 67 | } 68 | 69 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResourceList. 70 | func (in *MyResourceList) DeepCopy() *MyResourceList { 71 | if in == nil { 72 | return nil 73 | } 74 | out := new(MyResourceList) 75 | in.DeepCopyInto(out) 76 | return out 77 | } 78 | 79 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 80 | func (in *MyResourceList) DeepCopyObject() runtime.Object { 81 | if c := in.DeepCopy(); c != nil { 82 | return c 83 | } 84 | return nil 85 | } 86 | 87 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 88 | func (in *MyResourceSpec) DeepCopyInto(out *MyResourceSpec) { 89 | *out = *in 90 | out.Memory = in.Memory.DeepCopy() 91 | } 92 | 93 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResourceSpec. 94 | func (in *MyResourceSpec) DeepCopy() *MyResourceSpec { 95 | if in == nil { 96 | return nil 97 | } 98 | out := new(MyResourceSpec) 99 | in.DeepCopyInto(out) 100 | return out 101 | } 102 | 103 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 104 | func (in *MyResourceStatus) DeepCopyInto(out *MyResourceStatus) { 105 | *out = *in 106 | } 107 | 108 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResourceStatus. 109 | func (in *MyResourceStatus) DeepCopy() *MyResourceStatus { 110 | if in == nil { 111 | return nil 112 | } 113 | out := new(MyResourceStatus) 114 | in.DeepCopyInto(out) 115 | return out 116 | } 117 | -------------------------------------------------------------------------------- /ch13/myresource-kb/api/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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.myid.dev 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.myid.dev", 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 | -------------------------------------------------------------------------------- /ch13/myresource-kb/api/v1beta1/myresource_conversion.go: -------------------------------------------------------------------------------- 1 | package v1beta1 2 | 3 | import ( 4 | "github.com/myid/myresource/api/v1alpha1" 5 | "sigs.k8s.io/controller-runtime/pkg/conversion" 6 | ) 7 | 8 | func (src *MyResource) ConvertTo( 9 | dstRaw conversion.Hub, 10 | ) error { 11 | dst := dstRaw.(*v1alpha1.MyResource) 12 | dst.Spec.Memory = src.Spec.MemoryRequest 13 | // Copy other fields 14 | dst.ObjectMeta = src.ObjectMeta 15 | dst.Spec.Image = src.Spec.Image 16 | dst.Status.State = src.Status.State 17 | return nil 18 | } 19 | 20 | func (dst *MyResource) ConvertFrom( 21 | srcRaw conversion.Hub, 22 | ) error { 23 | src := srcRaw.(*v1alpha1.MyResource) 24 | dst.Spec.MemoryRequest = src.Spec.Memory 25 | // Copy other fields 26 | dst.ObjectMeta = src.ObjectMeta 27 | dst.Spec.Image = src.Spec.Image 28 | dst.Status.State = src.Status.State 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /ch13/myresource-kb/api/v1beta1/myresource_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | "k8s.io/apimachinery/pkg/api/resource" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 25 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 26 | 27 | // MyResourceSpec defines the desired state of MyResource 28 | type MyResourceSpec struct { 29 | Image string `json:"image"` 30 | MemoryRequest resource.Quantity `json:"memoryRequest"` 31 | } 32 | 33 | // MyResourceStatus defines the observed state of MyResource 34 | type MyResourceStatus struct { 35 | State string `json:"state"` 36 | } 37 | 38 | //+kubebuilder:object:root=true 39 | //+kubebuilder:subresource:status 40 | //+kubebuilder:printcolumn:name="Image",type=string,JSONPath=`.spec.image` 41 | //+kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state` 42 | //+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 43 | 44 | // MyResource is the Schema for the myresources API 45 | type MyResource struct { 46 | metav1.TypeMeta `json:",inline"` 47 | metav1.ObjectMeta `json:"metadata,omitempty"` 48 | 49 | Spec MyResourceSpec `json:"spec,omitempty"` 50 | Status MyResourceStatus `json:"status,omitempty"` 51 | } 52 | 53 | //+kubebuilder:object:root=true 54 | 55 | // MyResourceList contains a list of MyResource 56 | type MyResourceList struct { 57 | metav1.TypeMeta `json:",inline"` 58 | metav1.ListMeta `json:"metadata,omitempty"` 59 | Items []MyResource `json:"items"` 60 | } 61 | 62 | func init() { 63 | SchemeBuilder.Register(&MyResource{}, &MyResourceList{}) 64 | } 65 | -------------------------------------------------------------------------------- /ch13/myresource-kb/api/v1beta1/myresource_webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | ctrl "sigs.k8s.io/controller-runtime" 21 | logf "sigs.k8s.io/controller-runtime/pkg/log" 22 | ) 23 | 24 | // log is for logging in this package. 25 | var myresourcelog = logf.Log.WithName("myresource-resource") 26 | 27 | func (r *MyResource) SetupWebhookWithManager(mgr ctrl.Manager) error { 28 | return ctrl.NewWebhookManagedBy(mgr). 29 | For(r). 30 | Complete() 31 | } 32 | 33 | // TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 34 | -------------------------------------------------------------------------------- /ch13/myresource-kb/api/v1beta1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2022. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1beta1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *MyResource) DeepCopyInto(out *MyResource) { 30 | *out = *in 31 | out.TypeMeta = in.TypeMeta 32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 33 | in.Spec.DeepCopyInto(&out.Spec) 34 | out.Status = in.Status 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResource. 38 | func (in *MyResource) DeepCopy() *MyResource { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(MyResource) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 48 | func (in *MyResource) DeepCopyObject() runtime.Object { 49 | if c := in.DeepCopy(); c != nil { 50 | return c 51 | } 52 | return nil 53 | } 54 | 55 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 56 | func (in *MyResourceList) DeepCopyInto(out *MyResourceList) { 57 | *out = *in 58 | out.TypeMeta = in.TypeMeta 59 | in.ListMeta.DeepCopyInto(&out.ListMeta) 60 | if in.Items != nil { 61 | in, out := &in.Items, &out.Items 62 | *out = make([]MyResource, len(*in)) 63 | for i := range *in { 64 | (*in)[i].DeepCopyInto(&(*out)[i]) 65 | } 66 | } 67 | } 68 | 69 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResourceList. 70 | func (in *MyResourceList) DeepCopy() *MyResourceList { 71 | if in == nil { 72 | return nil 73 | } 74 | out := new(MyResourceList) 75 | in.DeepCopyInto(out) 76 | return out 77 | } 78 | 79 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 80 | func (in *MyResourceList) DeepCopyObject() runtime.Object { 81 | if c := in.DeepCopy(); c != nil { 82 | return c 83 | } 84 | return nil 85 | } 86 | 87 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 88 | func (in *MyResourceSpec) DeepCopyInto(out *MyResourceSpec) { 89 | *out = *in 90 | out.MemoryRequest = in.MemoryRequest.DeepCopy() 91 | } 92 | 93 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResourceSpec. 94 | func (in *MyResourceSpec) DeepCopy() *MyResourceSpec { 95 | if in == nil { 96 | return nil 97 | } 98 | out := new(MyResourceSpec) 99 | in.DeepCopyInto(out) 100 | return out 101 | } 102 | 103 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 104 | func (in *MyResourceStatus) DeepCopyInto(out *MyResourceStatus) { 105 | *out = *in 106 | } 107 | 108 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyResourceStatus. 109 | func (in *MyResourceStatus) DeepCopy() *MyResourceStatus { 110 | if in == nil { 111 | return nil 112 | } 113 | out := new(MyResourceStatus) 114 | in.DeepCopyInto(out) 115 | return out 116 | } 117 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | labels: 8 | app.kuberentes.io/name: issuer 9 | app.kubernetes.io/instance: selfsigned-issuer 10 | app.kubernetes.io/component: certificate 11 | app.kubernetes.io/created-by: myresource-kb 12 | app.kubernetes.io/part-of: myresource-kb 13 | app.kubernetes.io/managed-by: kustomize 14 | name: selfsigned-issuer 15 | namespace: system 16 | spec: 17 | selfSigned: {} 18 | --- 19 | apiVersion: cert-manager.io/v1 20 | kind: Certificate 21 | metadata: 22 | labels: 23 | app.kubernetes.io/name: certificate 24 | app.kubernetes.io/instance: serving-cert 25 | app.kubernetes.io/component: certificate 26 | app.kubernetes.io/created-by: myresource-kb 27 | app.kubernetes.io/part-of: myresource-kb 28 | app.kubernetes.io/managed-by: kustomize 29 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 30 | namespace: system 31 | spec: 32 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 33 | dnsNames: 34 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 35 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 36 | issuerRef: 37 | kind: Issuer 38 | name: selfsigned-issuer 39 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 40 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/crd/bases/mygroup.myid.dev_myresources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.9.2 7 | creationTimestamp: null 8 | name: myresources.mygroup.myid.dev 9 | spec: 10 | group: mygroup.myid.dev 11 | names: 12 | kind: MyResource 13 | listKind: MyResourceList 14 | plural: myresources 15 | singular: myresource 16 | scope: Namespaced 17 | versions: 18 | - additionalPrinterColumns: 19 | - jsonPath: .spec.image 20 | name: Image 21 | type: string 22 | - jsonPath: .status.state 23 | name: State 24 | type: string 25 | - jsonPath: .metadata.creationTimestamp 26 | name: Age 27 | type: date 28 | name: v1alpha1 29 | schema: 30 | openAPIV3Schema: 31 | description: MyResource is the Schema for the myresources API 32 | properties: 33 | apiVersion: 34 | description: 'APIVersion defines the versioned schema of this representation 35 | of an object. Servers should convert recognized schemas to the latest 36 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 37 | type: string 38 | kind: 39 | description: 'Kind is a string value representing the REST resource this 40 | object represents. Servers may infer this from the endpoint the client 41 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 42 | type: string 43 | metadata: 44 | type: object 45 | spec: 46 | description: MyResourceSpec defines the desired state of MyResource 47 | properties: 48 | image: 49 | type: string 50 | memory: 51 | anyOf: 52 | - type: integer 53 | - type: string 54 | pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ 55 | x-kubernetes-int-or-string: true 56 | required: 57 | - image 58 | - memory 59 | type: object 60 | status: 61 | description: MyResourceStatus defines the observed state of MyResource 62 | properties: 63 | state: 64 | type: string 65 | required: 66 | - state 67 | type: object 68 | type: object 69 | served: true 70 | storage: true 71 | subresources: 72 | status: {} 73 | - additionalPrinterColumns: 74 | - jsonPath: .spec.image 75 | name: Image 76 | type: string 77 | - jsonPath: .status.state 78 | name: State 79 | type: string 80 | - jsonPath: .metadata.creationTimestamp 81 | name: Age 82 | type: date 83 | name: v1beta1 84 | schema: 85 | openAPIV3Schema: 86 | description: MyResource is the Schema for the myresources API 87 | properties: 88 | apiVersion: 89 | description: 'APIVersion defines the versioned schema of this representation 90 | of an object. Servers should convert recognized schemas to the latest 91 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 92 | type: string 93 | kind: 94 | description: 'Kind is a string value representing the REST resource this 95 | object represents. Servers may infer this from the endpoint the client 96 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 97 | type: string 98 | metadata: 99 | type: object 100 | spec: 101 | description: MyResourceSpec defines the desired state of MyResource 102 | properties: 103 | image: 104 | type: string 105 | memoryRequest: 106 | anyOf: 107 | - type: integer 108 | - type: string 109 | pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ 110 | x-kubernetes-int-or-string: true 111 | required: 112 | - image 113 | - memoryRequest 114 | type: object 115 | status: 116 | description: MyResourceStatus defines the observed state of MyResource 117 | properties: 118 | state: 119 | type: string 120 | required: 121 | - state 122 | type: object 123 | type: object 124 | served: true 125 | storage: false 126 | subresources: 127 | status: {} 128 | -------------------------------------------------------------------------------- /ch13/myresource-kb/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.myid.dev_myresources.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | - patches/webhook_in_myresources.yaml 12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | - patches/cainjection_in_myresources.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 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/crd/patches/cainjection_in_myresources.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: myresources.mygroup.myid.dev 8 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/crd/patches/webhook_in_myresources.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: myresources.mygroup.myid.dev 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: myresource-kb-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: myresource-kb- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | - ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | - ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | 34 | 35 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 36 | # crd/kustomization.yaml 37 | - manager_webhook_patch.yaml 38 | 39 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 40 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 41 | # 'CERTMANAGER' needs to be enabled to use ca injection 42 | #- webhookcainjection_patch.yaml 43 | 44 | # the following config is for teaching kustomize how to do var substitution 45 | vars: 46 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 47 | - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 48 | objref: 49 | kind: Certificate 50 | group: cert-manager.io 51 | version: v1 52 | name: serving-cert # this name should match the one in certificate.yaml 53 | fieldref: 54 | fieldpath: metadata.namespace 55 | - name: CERTIFICATE_NAME 56 | objref: 57 | kind: Certificate 58 | group: cert-manager.io 59 | version: v1 60 | name: serving-cert # this name should match the one in certificate.yaml 61 | - name: SERVICE_NAMESPACE # namespace of the service 62 | objref: 63 | kind: Service 64 | version: v1 65 | name: webhook-service 66 | fieldref: 67 | fieldpath: metadata.namespace 68 | - name: SERVICE_NAME 69 | objref: 70 | kind: Service 71 | version: v1 72 | name: webhook-service 73 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | affinity: 12 | nodeAffinity: 13 | requiredDuringSchedulingIgnoredDuringExecution: 14 | nodeSelectorTerms: 15 | - matchExpressions: 16 | - key: kubernetes.io/arch 17 | operator: In 18 | values: 19 | - amd64 20 | - arm64 21 | - ppc64le 22 | - s390x 23 | - key: kubernetes.io/os 24 | operator: In 25 | values: 26 | - linux 27 | containers: 28 | - name: kube-rbac-proxy 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | capabilities: 32 | drop: 33 | - "ALL" 34 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0 35 | args: 36 | - "--secure-listen-address=0.0.0.0:8443" 37 | - "--upstream=http://127.0.0.1:8080/" 38 | - "--logtostderr=true" 39 | - "--v=0" 40 | ports: 41 | - containerPort: 8443 42 | protocol: TCP 43 | name: https 44 | resources: 45 | limits: 46 | cpu: 500m 47 | memory: 128Mi 48 | requests: 49 | cpu: 5m 50 | memory: 64Mi 51 | - name: manager 52 | args: 53 | - "--health-probe-bind-address=:8081" 54 | - "--metrics-bind-address=127.0.0.1:8080" 55 | - "--leader-elect" 56 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | -------------------------------------------------------------------------------- /ch13/myresource-kb/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: 9443 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 | -------------------------------------------------------------------------------- /ch13/myresource-kb/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/v1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | labels: 7 | app.kubernetes.io/name: mutatingwebhookconfiguration 8 | app.kubernetes.io/instance: mutating-webhook-configuration 9 | app.kubernetes.io/component: webhook 10 | app.kubernetes.io/created-by: myresource-kb 11 | app.kubernetes.io/part-of: myresource-kb 12 | app.kubernetes.io/managed-by: kustomize 13 | name: mutating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | --- 17 | apiVersion: admissionregistration.k8s.io/v1 18 | kind: ValidatingWebhookConfiguration 19 | metadata: 20 | labels: 21 | app.kubernetes.io/name: validatingwebhookconfiguration 22 | app.kubernetes.io/instance: validating-webhook-configuration 23 | app.kubernetes.io/component: webhook 24 | app.kubernetes.io/created-by: myresource-kb 25 | app.kubernetes.io/part-of: myresource-kb 26 | app.kubernetes.io/managed-by: kustomize 27 | name: validating-webhook-configuration 28 | annotations: 29 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 30 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: quay.io/myid/myresource 8 | newTag: v1alpha1-1 9 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: namespace 7 | app.kubernetes.io/instance: system 8 | app.kubernetes.io/component: manager 9 | app.kubernetes.io/created-by: myresource-kb 10 | app.kubernetes.io/part-of: myresource-kb 11 | app.kubernetes.io/managed-by: kustomize 12 | name: system 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: controller-manager 18 | namespace: system 19 | labels: 20 | control-plane: controller-manager 21 | app.kubernetes.io/name: deployment 22 | app.kubernetes.io/instance: controller-manager 23 | app.kubernetes.io/component: manager 24 | app.kubernetes.io/created-by: myresource-kb 25 | app.kubernetes.io/part-of: myresource-kb 26 | app.kubernetes.io/managed-by: kustomize 27 | spec: 28 | selector: 29 | matchLabels: 30 | control-plane: controller-manager 31 | replicas: 1 32 | template: 33 | metadata: 34 | annotations: 35 | kubectl.kubernetes.io/default-container: manager 36 | labels: 37 | control-plane: controller-manager 38 | spec: 39 | # TODO(user): Uncomment the following code to configure the nodeAffinity expression 40 | # according to the platforms which are supported by your solution. 41 | # It is considered best practice to support multiple architectures. You can 42 | # build your manager image using the makefile target docker-buildx. 43 | # affinity: 44 | # nodeAffinity: 45 | # requiredDuringSchedulingIgnoredDuringExecution: 46 | # nodeSelectorTerms: 47 | # - matchExpressions: 48 | # - key: kubernetes.io/arch 49 | # operator: In 50 | # values: 51 | # - amd64 52 | # - arm64 53 | # - ppc64le 54 | # - s390x 55 | # - key: kubernetes.io/os 56 | # operator: In 57 | # values: 58 | # - linux 59 | securityContext: 60 | runAsNonRoot: true 61 | # TODO(user): For common cases that do not require escalating privileges 62 | # it is recommended to ensure that all your Pods/Containers are restrictive. 63 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 64 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 65 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 66 | # seccompProfile: 67 | # type: RuntimeDefault 68 | containers: 69 | - command: 70 | - /manager 71 | args: 72 | - --leader-elect 73 | image: controller:latest 74 | name: manager 75 | securityContext: 76 | allowPrivilegeEscalation: false 77 | capabilities: 78 | drop: 79 | - "ALL" 80 | livenessProbe: 81 | httpGet: 82 | path: /healthz 83 | port: 8081 84 | initialDelaySeconds: 15 85 | periodSeconds: 20 86 | readinessProbe: 87 | httpGet: 88 | path: /readyz 89 | port: 8081 90 | initialDelaySeconds: 5 91 | periodSeconds: 10 92 | # TODO(user): Configure the resources accordingly based on the project requirements. 93 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 94 | resources: 95 | limits: 96 | cpu: 500m 97 | memory: 128Mi 98 | requests: 99 | cpu: 10m 100 | memory: 64Mi 101 | serviceAccountName: controller-manager 102 | terminationGracePeriodSeconds: 10 103 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | app.kubernetes.io/name: servicemonitor 9 | app.kubernetes.io/instance: controller-manager-metrics-monitor 10 | app.kubernetes.io/component: metrics 11 | app.kubernetes.io/created-by: myresource-kb 12 | app.kubernetes.io/part-of: myresource-kb 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controller-manager-metrics-monitor 15 | namespace: system 16 | spec: 17 | endpoints: 18 | - path: /metrics 19 | port: https 20 | scheme: https 21 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 22 | tlsConfig: 23 | insecureSkipVerify: true 24 | selector: 25 | matchLabels: 26 | control-plane: controller-manager 27 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: metrics-reader 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: myresource-kb 9 | app.kubernetes.io/part-of: myresource-kb 10 | app.kubernetes.io/managed-by: kustomize 11 | name: metrics-reader 12 | rules: 13 | - nonResourceURLs: 14 | - "/metrics" 15 | verbs: 16 | - get 17 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: proxy-role 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: myresource-kb 9 | app.kubernetes.io/part-of: myresource-kb 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-role 12 | rules: 13 | - apiGroups: 14 | - authentication.k8s.io 15 | resources: 16 | - tokenreviews 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - authorization.k8s.io 21 | resources: 22 | - subjectaccessreviews 23 | verbs: 24 | - create 25 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: proxy-rolebinding 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: myresource-kb 9 | app.kubernetes.io/part-of: myresource-kb 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: proxy-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: controller-manager-metrics-service 8 | app.kubernetes.io/component: kube-rbac-proxy 9 | app.kubernetes.io/created-by: myresource-kb 10 | app.kubernetes.io/part-of: myresource-kb 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-manager-metrics-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - name: https 17 | port: 8443 18 | protocol: TCP 19 | targetPort: https 20 | selector: 21 | control-plane: controller-manager 22 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /ch13/myresource-kb/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 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: myresource-kb 10 | app.kubernetes.io/part-of: myresource-kb 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: myresource-kb 9 | app.kubernetes.io/part-of: myresource-kb 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/myresource_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit myresources. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: myresource-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: myresource-kb 10 | app.kubernetes.io/part-of: myresource-kb 11 | app.kubernetes.io/managed-by: kustomize 12 | name: myresource-editor-role 13 | rules: 14 | - apiGroups: 15 | - mygroup.myid.dev 16 | resources: 17 | - myresources 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - mygroup.myid.dev 28 | resources: 29 | - myresources/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/myresource_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view myresources. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: myresource-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: myresource-kb 10 | app.kubernetes.io/part-of: myresource-kb 11 | app.kubernetes.io/managed-by: kustomize 12 | name: myresource-viewer-role 13 | rules: 14 | - apiGroups: 15 | - mygroup.myid.dev 16 | resources: 17 | - myresources 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - mygroup.myid.dev 24 | resources: 25 | - myresources/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: manager-role 7 | rules: 8 | - apiGroups: 9 | - apps 10 | resources: 11 | - deployments 12 | verbs: 13 | - create 14 | - delete 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - mygroup.myid.dev 22 | resources: 23 | - myresources 24 | verbs: 25 | - create 26 | - delete 27 | - get 28 | - list 29 | - patch 30 | - update 31 | - watch 32 | - apiGroups: 33 | - mygroup.myid.dev 34 | resources: 35 | - myresources/finalizers 36 | verbs: 37 | - update 38 | - apiGroups: 39 | - mygroup.myid.dev 40 | resources: 41 | - myresources/status 42 | verbs: 43 | - get 44 | - patch 45 | - update 46 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: manager-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: myresource-kb 9 | app.kubernetes.io/part-of: myresource-kb 10 | app.kubernetes.io/managed-by: kustomize 11 | name: manager-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: manager-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: serviceaccount 6 | app.kuberentes.io/instance: controller-manager 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: myresource-kb 9 | app.kubernetes.io/part-of: myresource-kb 10 | app.kubernetes.io/managed-by: kustomize 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/samples/mygroup_v1alpha1_myresource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: mygroup.myid.dev/v1alpha1 2 | kind: MyResource 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: myresource 6 | app.kubernetes.io/instance: myresource-sample 7 | app.kubernetes.io/part-of: myresource-kb 8 | app.kuberentes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: myresource-kb 10 | name: myresource-sample 11 | spec: 12 | image: nginx 13 | memory: 512Mi 14 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/samples/mygroup_v1beta1_myresource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: mygroup.myid.dev/v1beta1 2 | kind: MyResource 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: myresource 6 | app.kubernetes.io/instance: myresource-sample 7 | app.kubernetes.io/part-of: myresource-kb 8 | app.kuberentes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: myresource-kb 10 | name: myresource-sample 11 | spec: 12 | # TODO(user): Add fields here 13 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | #- manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /ch13/myresource-kb/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 | -------------------------------------------------------------------------------- /ch13/myresource-kb/config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: webhook-service 8 | app.kubernetes.io/component: webhook 9 | app.kubernetes.io/created-by: myresource-kb 10 | app.kubernetes.io/part-of: myresource-kb 11 | app.kubernetes.io/managed-by: kustomize 12 | name: webhook-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - port: 443 17 | protocol: TCP 18 | targetPort: 9443 19 | selector: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /ch13/myresource-kb/controllers/deployment.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | mygroupv1alpha1 "github.com/myid/myresource/api/v1alpha1" 7 | appsv1 "k8s.io/api/apps/v1" 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | ) 12 | 13 | func (a *MyResourceReconciler) applyDeployment( 14 | ctx context.Context, 15 | myres *mygroupv1alpha1.MyResource, 16 | ownerref *metav1.OwnerReference, 17 | ) error { 18 | deploy := createDeployment(myres, ownerref) 19 | err := a.Client.Patch( 20 | ctx, 21 | deploy, 22 | client.Apply, 23 | client.FieldOwner(Name), 24 | client.ForceOwnership, 25 | ) 26 | //generation := deploy.GetGeneration() 27 | //if generation == 1 { 28 | // a.EventRecorder.Eventf(myres, corev1.EventTypeNormal, "DeploymentCreated", "The deployment %q has been created", deploy.GetName()) 29 | //} 30 | return err 31 | } 32 | 33 | func createDeployment( 34 | myres *mygroupv1alpha1.MyResource, 35 | ownerref *metav1.OwnerReference, 36 | ) *appsv1.Deployment { 37 | deploy := &appsv1.Deployment{ 38 | ObjectMeta: metav1.ObjectMeta{ 39 | Labels: map[string]string{ 40 | "myresource": myres.GetName(), 41 | }, 42 | }, 43 | Spec: appsv1.DeploymentSpec{ 44 | Selector: &metav1.LabelSelector{ 45 | MatchLabels: map[string]string{ 46 | "myresource": myres.GetName(), 47 | }, 48 | }, 49 | Template: corev1.PodTemplateSpec{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Labels: map[string]string{ 52 | "myresource": myres.GetName(), 53 | }, 54 | }, 55 | Spec: corev1.PodSpec{ 56 | Containers: []corev1.Container{ 57 | { 58 | Name: "main", 59 | Image: myres.Spec.Image, 60 | Resources: corev1.ResourceRequirements{ 61 | Requests: corev1.ResourceList{ 62 | corev1.ResourceMemory: myres.Spec.Memory, 63 | }, 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | } 71 | deploy.SetName(myres.GetName() + "-deployment") 72 | deploy.SetNamespace(myres.GetNamespace()) 73 | deploy.SetGroupVersionKind( 74 | appsv1.SchemeGroupVersion.WithKind("Deployment"), 75 | ) 76 | deploy.SetOwnerReferences([]metav1.OwnerReference{ 77 | *ownerref, 78 | }) 79 | return deploy 80 | } 81 | -------------------------------------------------------------------------------- /ch13/myresource-kb/controllers/myresource_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | "k8s.io/apimachinery/pkg/api/errors" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | ctrl "sigs.k8s.io/controller-runtime" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/log" 28 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 29 | 30 | mygroupv1alpha1 "github.com/myid/myresource/api/v1alpha1" 31 | ) 32 | 33 | const ( 34 | Name = "myresource-controller" 35 | ) 36 | 37 | // MyResourceReconciler reconciles a MyResource object 38 | type MyResourceReconciler struct { 39 | client.Client 40 | Scheme *runtime.Scheme 41 | } 42 | 43 | //+kubebuilder:rbac:groups=mygroup.myid.dev,resources=myresources,verbs=get;list;watch;create;update;patch;delete 44 | //+kubebuilder:rbac:groups=mygroup.myid.dev,resources=myresources/status,verbs=get;update;patch 45 | //+kubebuilder:rbac:groups=mygroup.myid.dev,resources=myresources/finalizers,verbs=update 46 | //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete 47 | 48 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 49 | // move the current state of the cluster closer to the desired state. 50 | // TODO(user): Modify the Reconcile function to compare the state specified by 51 | // the MyResource object against the actual cluster state, and then 52 | // perform operations to make the cluster state reflect the state specified by 53 | // the user. 54 | // 55 | // For more details, check Reconcile and its Result here: 56 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile 57 | func (r *MyResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 58 | log := log.FromContext(ctx) 59 | log.Info("getting myresource instance") 60 | 61 | myresource := mygroupv1alpha1.MyResource{} 62 | err := r.Client.Get( 63 | ctx, 64 | req.NamespacedName, 65 | &myresource, 66 | &client.GetOptions{}, 67 | ) 68 | if err != nil { 69 | if errors.IsNotFound(err) { 70 | log.Info("resource is not found") 71 | return reconcile.Result{}, nil 72 | } 73 | return reconcile.Result{}, err 74 | } 75 | 76 | ownerReference := metav1.NewControllerRef( 77 | &myresource, 78 | mygroupv1alpha1.GroupVersion.WithKind("MyResource"), 79 | ) 80 | 81 | err = r.applyDeployment(ctx, &myresource, ownerReference) 82 | if err != nil { 83 | return reconcile.Result{}, err 84 | } 85 | 86 | status, err := r.computeStatus(ctx, &myresource) 87 | if err != nil { 88 | return reconcile.Result{}, err 89 | } 90 | myresource.Status = *status 91 | log.Info("updating status", "state", status.State) 92 | err = r.Client.Status().Update(ctx, &myresource) 93 | if err != nil { 94 | return reconcile.Result{}, err 95 | } 96 | 97 | return ctrl.Result{}, nil 98 | } 99 | 100 | // SetupWithManager sets up the controller with the Manager. 101 | func (r *MyResourceReconciler) SetupWithManager(mgr ctrl.Manager) error { 102 | return ctrl.NewControllerManagedBy(mgr). 103 | For(&mygroupv1alpha1.MyResource{}). 104 | Complete(r) 105 | } 106 | -------------------------------------------------------------------------------- /ch13/myresource-kb/controllers/reconcile_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | mygroupv1alpha1 "github.com/myid/myresource/api/v1alpha1" 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | appsv1 "k8s.io/api/apps/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/types" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | ) 15 | 16 | var _ = Describe("MyResource controller", func() { 17 | When("When creating a MyResource instance", func() { 18 | 19 | var ( 20 | myres mygroupv1alpha1.MyResource 21 | ownerref *metav1.OwnerReference 22 | name string 23 | namespace = "default" 24 | deployName string 25 | image string 26 | ) 27 | 28 | BeforeEach(func() { 29 | image = fmt.Sprintf("myimage%d", rand.Intn(1000)) 30 | myres = mygroupv1alpha1.MyResource{ 31 | Spec: mygroupv1alpha1.MyResourceSpec{ 32 | Image: image, 33 | }, 34 | } 35 | name = fmt.Sprintf("myres%d", rand.Intn(1000)) 36 | myres.SetName(name) 37 | myres.SetNamespace(namespace) 38 | err := k8sClient.Create(ctx, &myres) 39 | Expect(err).NotTo(HaveOccurred()) 40 | ownerref = metav1.NewControllerRef( 41 | &myres, 42 | mygroupv1alpha1.GroupVersion.WithKind("MyResource"), 43 | ) 44 | deployName = fmt.Sprintf("%s-deployment", name) 45 | }) 46 | 47 | AfterEach(func() { 48 | k8sClient.Delete(ctx, &myres) 49 | }) 50 | 51 | It("should create a deployment", func() { 52 | var dep appsv1.Deployment 53 | Eventually(deploymentExists(deployName, namespace, &dep), 10, 1). 54 | Should(BeTrue()) 55 | }) 56 | 57 | When("deployment is found", func() { 58 | var dep appsv1.Deployment 59 | 60 | BeforeEach(func() { 61 | Eventually( 62 | deploymentExists(deployName, namespace, &dep), 10, 1, 63 | ).Should(BeTrue()) 64 | }) 65 | 66 | It("should be owned by the MyResource instance", func() { 67 | Expect(dep.GetOwnerReferences()). 68 | To(ContainElement(*ownerref)) 69 | }) 70 | 71 | It("should use the image specified in MyResource instance", func() { 72 | Expect(dep.Spec.Template.Spec.Containers[0].Image). 73 | To(Equal(image)) 74 | }) 75 | 76 | When("deployment ReadyReplicas is 1", func() { 77 | BeforeEach(func() { 78 | dep.Status.Replicas = 1 79 | dep.Status.ReadyReplicas = 1 80 | err := k8sClient.Status().Update(ctx, &dep) 81 | Expect(err).NotTo(HaveOccurred()) 82 | }) 83 | 84 | It("should set status ready for MyResource instance", func() { 85 | Eventually(getMyResourceState(name, namespace), 10, 1). 86 | Should(Equal("Ready")) 87 | }) 88 | }) 89 | }) 90 | }) 91 | }) 92 | 93 | func deploymentExists(name, namespace string, dep *appsv1.Deployment) func() bool { 94 | return func() bool { 95 | err := k8sClient.Get(ctx, client.ObjectKey{ 96 | Namespace: namespace, 97 | Name: name, 98 | }, dep) 99 | return err == nil 100 | } 101 | } 102 | 103 | func getMyResourceState(name, namespace string) func() (string, error) { 104 | return func() (string, error) { 105 | myres := mygroupv1alpha1.MyResource{} 106 | err := k8sClient.Get(ctx, types.NamespacedName{ 107 | Namespace: namespace, 108 | Name: name, 109 | }, &myres) 110 | if err != nil { 111 | return "", err 112 | } 113 | return myres.Status.State, nil 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /ch13/myresource-kb/controllers/status.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | mygroupv1alpha1 "github.com/myid/myresource/api/v1alpha1" 8 | appsv1 "k8s.io/api/apps/v1" 9 | "sigs.k8s.io/controller-runtime/pkg/client" 10 | "sigs.k8s.io/controller-runtime/pkg/log" 11 | ) 12 | 13 | const ( 14 | _buildingState = "Building" 15 | _readyState = "Ready" 16 | ) 17 | 18 | func (a *MyResourceReconciler) computeStatus( 19 | ctx context.Context, 20 | myres *mygroupv1alpha1.MyResource, 21 | ) (*mygroupv1alpha1.MyResourceStatus, error) { 22 | 23 | logger := log.FromContext(ctx) 24 | result := mygroupv1alpha1.MyResourceStatus{ 25 | State: _buildingState, 26 | } 27 | 28 | deployList := appsv1.DeploymentList{} 29 | err := a.Client.List( 30 | ctx, 31 | &deployList, 32 | client.InNamespace(myres.GetNamespace()), 33 | client.MatchingLabels{ 34 | "myresource": myres.GetName(), 35 | }, 36 | ) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | if len(deployList.Items) == 0 { 42 | logger.Info("no deployment found") 43 | return &result, nil 44 | } 45 | 46 | if len(deployList.Items) > 1 { 47 | logger.Info("too many deployments found", "count", 48 | len(deployList.Items)) 49 | return nil, fmt.Errorf("%d deployment found, expected 1", 50 | len(deployList.Items)) 51 | } 52 | 53 | status := deployList.Items[0].Status 54 | logger.Info("got deployment status", "status", status) 55 | if status.ReadyReplicas == 1 { 56 | result.State = _readyState 57 | } 58 | 59 | return &result, nil 60 | } 61 | -------------------------------------------------------------------------------- /ch13/myresource-kb/controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | "path/filepath" 22 | "testing" 23 | 24 | . "github.com/onsi/ginkgo/v2" 25 | . "github.com/onsi/gomega" 26 | 27 | appsv1 "k8s.io/api/apps/v1" 28 | "k8s.io/client-go/kubernetes/scheme" 29 | "k8s.io/client-go/rest" 30 | "sigs.k8s.io/controller-runtime/pkg/builder" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | "sigs.k8s.io/controller-runtime/pkg/envtest" 33 | logf "sigs.k8s.io/controller-runtime/pkg/log" 34 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 35 | "sigs.k8s.io/controller-runtime/pkg/manager" 36 | 37 | mygroupv1alpha1 "github.com/myid/myresource/api/v1alpha1" 38 | //+kubebuilder:scaffold:imports 39 | ) 40 | 41 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 42 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 43 | 44 | var cfg *rest.Config 45 | var k8sClient client.Client 46 | var testEnv *envtest.Environment 47 | var ctx context.Context 48 | var cancel context.CancelFunc 49 | 50 | func TestAPIs(t *testing.T) { 51 | RegisterFailHandler(Fail) 52 | 53 | RunSpecs(t, "Controller Suite") 54 | } 55 | 56 | var _ = BeforeSuite(func() { 57 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 58 | 59 | ctx, cancel = context.WithCancel(context.Background()) 60 | 61 | By("bootstrapping test environment") 62 | testEnv = &envtest.Environment{ 63 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 64 | ErrorIfCRDPathMissing: true, 65 | } 66 | 67 | var err error 68 | // cfg is defined in this file globally. 69 | cfg, err = testEnv.Start() 70 | Expect(err).NotTo(HaveOccurred()) 71 | Expect(cfg).NotTo(BeNil()) 72 | 73 | err = mygroupv1alpha1.AddToScheme(scheme.Scheme) 74 | Expect(err).NotTo(HaveOccurred()) 75 | 76 | //+kubebuilder:scaffold:scheme 77 | 78 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 79 | Expect(err).NotTo(HaveOccurred()) 80 | Expect(k8sClient).NotTo(BeNil()) 81 | 82 | mgr, err := manager.New(cfg, manager.Options{ 83 | Scheme: scheme.Scheme, 84 | }) 85 | 86 | Expect(err).ToNot(HaveOccurred()) 87 | err = builder. 88 | ControllerManagedBy(mgr). 89 | Named(Name). 90 | For(&mygroupv1alpha1.MyResource{}). 91 | Owns(&appsv1.Deployment{}). 92 | Complete(&MyResourceReconciler{ 93 | Client: mgr.GetClient(), 94 | Scheme: mgr.GetScheme(), 95 | }) 96 | 97 | go func() { 98 | defer GinkgoRecover() 99 | err = mgr.Start(ctx) 100 | Expect(err).ToNot(HaveOccurred(), "failed to run manager") 101 | }() 102 | }) 103 | 104 | var _ = AfterSuite(func() { 105 | By("tearing down the test environment") 106 | cancel() 107 | err := testEnv.Stop() 108 | Expect(err).NotTo(HaveOccurred()) 109 | }) 110 | -------------------------------------------------------------------------------- /ch13/myresource-kb/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/myid/myresource 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.1.4 7 | github.com/onsi/gomega v1.19.0 8 | k8s.io/api v0.25.0 9 | k8s.io/apimachinery v0.25.0 10 | k8s.io/client-go v0.25.0 11 | sigs.k8s.io/controller-runtime v0.13.0 12 | ) 13 | 14 | require ( 15 | cloud.google.com/go v0.97.0 // indirect 16 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 17 | github.com/Azure/go-autorest/autorest v0.11.27 // indirect 18 | github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect 19 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 20 | github.com/Azure/go-autorest/logger v0.2.1 // indirect 21 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 22 | github.com/PuerkitoBio/purell v1.1.1 // indirect 23 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 24 | github.com/beorn7/perks v1.0.1 // indirect 25 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 26 | github.com/davecgh/go-spew v1.1.1 // indirect 27 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 28 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 29 | github.com/fsnotify/fsnotify v1.5.4 // indirect 30 | github.com/go-logr/logr v1.2.3 // indirect 31 | github.com/go-logr/zapr v1.2.3 // indirect 32 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 33 | github.com/go-openapi/jsonreference v0.19.5 // indirect 34 | github.com/go-openapi/swag v0.19.14 // indirect 35 | github.com/gogo/protobuf v1.3.2 // indirect 36 | github.com/golang-jwt/jwt/v4 v4.2.0 // indirect 37 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 38 | github.com/golang/protobuf v1.5.2 // indirect 39 | github.com/google/gnostic v0.5.7-v3refs // indirect 40 | github.com/google/go-cmp v0.5.8 // indirect 41 | github.com/google/gofuzz v1.1.0 // indirect 42 | github.com/google/uuid v1.1.2 // indirect 43 | github.com/imdario/mergo v0.3.12 // indirect 44 | github.com/josharian/intern v1.0.0 // indirect 45 | github.com/json-iterator/go v1.1.12 // indirect 46 | github.com/mailru/easyjson v0.7.6 // indirect 47 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 49 | github.com/modern-go/reflect2 v1.0.2 // indirect 50 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 51 | github.com/pkg/errors v0.9.1 // indirect 52 | github.com/prometheus/client_golang v1.12.2 // indirect 53 | github.com/prometheus/client_model v0.2.0 // indirect 54 | github.com/prometheus/common v0.32.1 // indirect 55 | github.com/prometheus/procfs v0.7.3 // indirect 56 | github.com/spf13/pflag v1.0.5 // indirect 57 | go.uber.org/atomic v1.7.0 // indirect 58 | go.uber.org/multierr v1.6.0 // indirect 59 | go.uber.org/zap v1.21.0 // indirect 60 | golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect 61 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 62 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 63 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 64 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 65 | golang.org/x/text v0.3.7 // indirect 66 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 67 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 68 | google.golang.org/appengine v1.6.7 // indirect 69 | google.golang.org/protobuf v1.28.0 // indirect 70 | gopkg.in/inf.v0 v0.9.1 // indirect 71 | gopkg.in/yaml.v2 v2.4.0 // indirect 72 | gopkg.in/yaml.v3 v3.0.1 // indirect 73 | k8s.io/apiextensions-apiserver v0.25.0 // indirect 74 | k8s.io/component-base v0.25.0 // indirect 75 | k8s.io/klog/v2 v2.70.1 // indirect 76 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 77 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 78 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 79 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 80 | sigs.k8s.io/yaml v1.3.0 // indirect 81 | ) 82 | -------------------------------------------------------------------------------- /ch13/myresource-kb/hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | */ -------------------------------------------------------------------------------- /ch13/myresource-kb/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 24 | // to ensure that exec-entrypoint and run can make use of them. 25 | _ "k8s.io/client-go/plugin/pkg/client/auth" 26 | 27 | "k8s.io/apimachinery/pkg/runtime" 28 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 29 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 30 | ctrl "sigs.k8s.io/controller-runtime" 31 | "sigs.k8s.io/controller-runtime/pkg/healthz" 32 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 33 | 34 | mygroupv1alpha1 "github.com/myid/myresource/api/v1alpha1" 35 | mygroupv1beta1 "github.com/myid/myresource/api/v1beta1" 36 | "github.com/myid/myresource/controllers" 37 | //+kubebuilder:scaffold:imports 38 | ) 39 | 40 | var ( 41 | scheme = runtime.NewScheme() 42 | setupLog = ctrl.Log.WithName("setup") 43 | ) 44 | 45 | func init() { 46 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 47 | 48 | utilruntime.Must(mygroupv1alpha1.AddToScheme(scheme)) 49 | utilruntime.Must(mygroupv1beta1.AddToScheme(scheme)) 50 | //+kubebuilder:scaffold:scheme 51 | } 52 | 53 | func main() { 54 | var metricsAddr string 55 | var enableLeaderElection bool 56 | var probeAddr string 57 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 58 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 59 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 60 | "Enable leader election for controller manager. "+ 61 | "Enabling this will ensure there is only one active controller manager.") 62 | opts := zap.Options{ 63 | Development: true, 64 | } 65 | opts.BindFlags(flag.CommandLine) 66 | flag.Parse() 67 | 68 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 69 | 70 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 71 | Scheme: scheme, 72 | MetricsBindAddress: metricsAddr, 73 | Port: 9443, 74 | HealthProbeBindAddress: probeAddr, 75 | LeaderElection: enableLeaderElection, 76 | LeaderElectionID: "ab35bae8.myid.dev", 77 | // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily 78 | // when the Manager ends. This requires the binary to immediately end when the 79 | // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly 80 | // speeds up voluntary leader transitions as the new leader don't have to wait 81 | // LeaseDuration time first. 82 | // 83 | // In the default scaffold provided, the program ends immediately after 84 | // the manager stops, so would be fine to enable this option. However, 85 | // if you are doing or is intended to do any operation such as perform cleanups 86 | // after the manager stops then its usage might be unsafe. 87 | // LeaderElectionReleaseOnCancel: true, 88 | }) 89 | if err != nil { 90 | setupLog.Error(err, "unable to start manager") 91 | os.Exit(1) 92 | } 93 | 94 | if err = (&controllers.MyResourceReconciler{ 95 | Client: mgr.GetClient(), 96 | Scheme: mgr.GetScheme(), 97 | }).SetupWithManager(mgr); err != nil { 98 | setupLog.Error(err, "unable to create controller", "controller", "MyResource") 99 | os.Exit(1) 100 | } 101 | if err = (&mygroupv1beta1.MyResource{}).SetupWebhookWithManager(mgr); err != nil { 102 | setupLog.Error(err, "unable to create webhook", "webhook", "MyResource") 103 | os.Exit(1) 104 | } 105 | //+kubebuilder:scaffold:builder 106 | 107 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 108 | setupLog.Error(err, "unable to set up health check") 109 | os.Exit(1) 110 | } 111 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 112 | setupLog.Error(err, "unable to set up ready check") 113 | os.Exit(1) 114 | } 115 | 116 | setupLog.Info("starting manager") 117 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 118 | setupLog.Error(err, "problem running manager") 119 | os.Exit(1) 120 | } 121 | } 122 | --------------------------------------------------------------------------------