Change:
What would you like to be : Add documentation/example showing the usage of dynamic package in the client-go repo. /sig api-machinery

Discussion:
"//staging/src/k8s.io/client-go/discovery:all-srcs", "//staging/src/k8s.io/client-go/dynamic:all-srcs", "//staging/src/k8s.io/client-go/examples/create-update-delete-deployment:all-srcs", <ins> "//staging/src/k8s.io/client-go/examples/dynamic-create-update-delete-deployment:all-srcs", </ins> "//staging/src/k8s.io/client-go/examples/fake-client:all-srcs", "//staging/src/k8s.io/client-go/examples/in-cluster-client-configuration:all-srcs", "//staging/src/k8s.io/client-go/examples/leader-election:all-srcs",

<ins> load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") go_library( name = "go_default_library", srcs = ["main.go"], importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/examples/dynamic-create-update-delete-deployment", importpath = "k8s.io/client-go/examples/dynamic-create-update-delete-deployment", visibility = ["//visibility:private"], deps = [ "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/client-go/dynamic:go_default_library", "//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library", "//staging/src/k8s.io/client-go/util/homedir:go_default_library", "//staging/src/k8s.io/client-go/util/retry:go_default_library", ], ) go_binary( name = "dynamic-create-update-delete-deployment", embed = [":go_default_library"], visibility = ["//visibility:public"], ) filegroup( name = "package-srcs", srcs = glob(["**"]), tags = ["automanaged"], visibility = ["//visibility:private"], ) filegroup( name = "all-srcs", srcs = [":package-srcs"], tags = ["automanaged"], visibility = ["//visibility:public"], ) </ins>

<ins> # Create, Update & Delete Deployment with the Dynamic Package This example program demonstrates the fundamental operations for managing on [Deployment][1] resources, such as `Create`, `List`, `Update` and `Delete` using client-go's `dynamic` package. ## Typed Vs. Dynamic The code in this directory is based on a similar [example that uses Kubernetes typed client sets][2]. The typed client sets make it simple to communicate with the API server using pre-generated local API objects to achieve an RPC-like programming experience.  Typed clients uses program compilations to enforce data safety and some validation.  However, when using typed clients, programs are forced to be tightly coupled with the version and the types used. The `dynamic` package on the other hand, uses a simple type, `unstructured.Unstructured`, to represent all object values from the API server. Type `Unstructured` uses a collection of nested `map[string]interface{}` values to create an internal structure that closely resemble the REST payload from the server. The dynamic package defers all data bindings until runtime.  This means programs that use the dynamic client will not get any of the benefits of type validation until the program is running.  This may be a problem for certain types of applications that require strong data type check and validation. Being loosely coupled, however, means that programs that uses the `dynamic` package do not require recompilation when the client API changes.  The client program has more flexibility in handling updates to the API surface without knowing ahead of time what those changes are. ## Running this example Make sure you have a Kubernetes cluster and `kubectl` is configured: ``` kubectl get nodes ``` Compile this example on your workstation: ``` cd dynamic-create-update-delete-deployment go build -o ./app ``` Now, run this application on your workstation with your local kubeconfig file: ``` ./app # or specify a kubeconfig file with flag ./app -kubeconfig=$HOME/.kube/config ``` Running this command will execute the following operations on your cluster: 1. **Create Deployment:** This will create a 2 replica Deployment. Verify with `kubectl get pods`. 2. **Update Deployment:** This will update the Deployment resource created in previous step by setting the replica count to 1 and changing the container image to `nginx:1.13`. You are encouraged to inspect the retry loop that handles conflicts. Verify the new replica count and container image with `kubectl describe deployment demo`. 3. **List Deployments:** This will retrieve Deployments in the `default` namespace and print their names and replica counts. 4. **Delete Deployment:** This will delete the Deployment object and its dependent ReplicaSet resource. Verify with `kubectl get deployments`. Each step is separated by an interactive prompt. You must hit the <kbd>Return</kbd> key to proceed to the next step. You can use these prompts as a break to take time to run `kubectl` and inspect the result of the operations executed. You should see an output like the following: ``` Creating deployment... Created deployment "demo-deployment". -> Press Return key to continue. Updating deployment... Updated deployment... -> Press Return key to continue. Listing deployments in namespace "default": * demo-deployment (1 replicas) -> Press Return key to continue. Deleting deployment... Deleted deployment. ``` ## Cleanup Successfully running this program will clean the created artifacts. If you terminate the program without completing, you can clean up the created deployment with: kubectl delete deploy demo-deployment ## Troubleshooting If you are getting the following error, make sure Kubernetes version of your cluster is v1.13 or higher in `kubectl version`: panic: the server could not find the requested resource [1]: https://kubernetes.io/docs/user-guide/deployments/ [2]: ../create-update-delete-deployment </ins>

<ins> /* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Note: the example only works with the code within the same release/branch. package main import ( "bufio" "flag" "fmt" "os" "path/filepath" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" "k8s.io/client-go/util/retry" // // Uncomment to load all auth plugins // _ "k8s.io/client-go/plugin/pkg/client/auth // // Or uncomment to load specific auth plugins // _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) func main() { var kubeconfig *string if home := homedir.HomeDir(); home != "" { kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") } else { kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file") } flag.Parse() namespace := "default" config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) if err != nil { panic(err) } client, err := dynamic.NewForConfig(config) if err != nil { panic(err) } deploymentRes := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"} deployment := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind":       "Deployment", "metadata": map[string]interface{}{ "name": "demo-deployment", }, "spec": map[string]interface{}{ "replicas": 2, "selector": map[string]interface{}{ "matchLabels": map[string]interface{}{ "app": "demo", }, }, "template": map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{ "app": "demo", }, }, "spec": map[string]interface{}{ "containers": []map[string]interface{}{ { "name":  "web", "image": "nginx:1.12", "ports": []map[string]interface{}{ { "name":          "http", "protocol":      "TCP", "containerPort": 80, }, }, }, }, }, }, }, }, } // Create Deployment fmt.Println("Creating deployment...") result, err := client.Resource(deploymentRes).Namespace(namespace).Create(deployment, metav1.CreateOptions{}) if err != nil { panic(err) } fmt.Printf("Created deployment %q.n", result.GetName()) // Update Deployment prompt() fmt.Println("Updating deployment...") //    You have two options to Update() this Deployment: // //    1. Modify the "deployment" variable and call: Update(deployment). //       This works like the "kubectl replace" command and it overwrites/loses changes //       made by other clients between you Create() and Update() the object. //    2. Modify the "result" returned by Get() and retry Update(result) until //       you no longer get a conflict error. This way, you can preserve changes made //       by other clients between Create() and Update(). This is implemented below //			 using the retry utility package included with client-go. (RECOMMENDED) // // More Info: // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { // Retrieve the latest version of Deployment before attempting update // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver result, getErr := client.Resource(deploymentRes).Namespace(namespace).Get("demo-deployment", metav1.GetOptions{}) if getErr != nil { panic(fmt.Errorf("Failed to get latest version of Deployment: %v", getErr)) } // update replicas to 1 if err := unstructured.SetNestedField(result.Object, int64(1), "spec", "replicas"); err != nil { panic(fmt.Errorf("Failed to set replica value: %v", err)) } // extract spec containers containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") if err != nil || !found || containers == nil { panic(fmt.Errorf("Deployment containers not found or error in spec: %v", err)) } // update container[0] image if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), "nginx:1.13", "image"); err != nil { panic(err) } if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { panic(err) } _, updateErr := client.Resource(deploymentRes).Namespace(namespace).Update(result, metav1.UpdateOptions{}) return updateErr }) if retryErr != nil { panic(fmt.Errorf("Update failed: %v", retryErr)) } fmt.Println("Updated deployment...") // List Deployments prompt() fmt.Printf("Listing deployments in namespace %q:n", apiv1.NamespaceDefault) list, err := client.Resource(deploymentRes).Namespace(namespace).List(metav1.ListOptions{}) if err != nil { panic(err) } for _, d := range list.Items { replicas, found, err := unstructured.NestedInt64(d.Object, "spec", "replicas") if err != nil || !found { fmt.Printf("Replicas not found for deployment %s: error=%s", d.GetName(), err) continue } fmt.Printf(" * %s (%d replicas)n", d.GetName(), replicas) } // Delete Deployment prompt() fmt.Println("Deleting deployment...") deletePolicy := metav1.DeletePropagationForeground deleteOptions := &metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, } if err := client.Resource(deploymentRes).Namespace(namespace).Delete("demo-deployment", deleteOptions); err != nil { panic(err) } fmt.Println("Deleted deployment.") } func prompt() { fmt.Printf("-> Press Return key to continue.") scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { break } if err := scanner.Err(); err != nil { panic(err) } fmt.Println() } </ins>