Last active
November 29, 2020 08:38
-
-
Save mittachaitu/43684da75994b4cdb094155aac3ff7f2 to your computer and use it in GitHub Desktop.
Following program will help to solve error(unable to find api field in struct Unstructured for the json field "spec"`) faced while patching unstructured object
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"encoding/json" | |
"flag" | |
jsonpatch "github.com/evanphx/json-patch" | |
"github.com/pkg/errors" | |
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | |
"k8s.io/apimachinery/pkg/runtime/schema" | |
"k8s.io/apimachinery/pkg/types" | |
"k8s.io/client-go/dynamic" | |
"k8s.io/client-go/rest" | |
"k8s.io/client-go/tools/clientcmd" | |
"k8s.io/klog/v2" | |
) | |
// factory to store clients | |
// which can be use to perform | |
// CRUD operations | |
type factory struct { | |
ctx context.Context | |
dynamicClient dynamic.Interface | |
} | |
// getClusterConfig return the config for k8s | |
func getClusterConfig(kubeconfig string) (*rest.Config, error) { | |
if kubeconfig != "" { | |
return clientcmd.BuildConfigFromFlags("", kubeconfig) | |
} | |
klog.Info("Kubeconfig flag is empty") | |
return rest.InClusterConfig() | |
} | |
func (f *factory) newDynamicClient(config *rest.Config) (*factory, error) { | |
var err error | |
f.dynamicClient, err = dynamic.NewForConfig(config) | |
if err != nil { | |
return nil, errors.Wrapf(err, "failed to get dynmic client") | |
} | |
return f, nil | |
} | |
func (f *factory) getObject(namespace, name string, gvr schema.GroupVersionResource) (*unstructured.Unstructured, error) { | |
return f.dynamicClient.Resource(gvr).Namespace(namespace).Get(f.ctx, name, metav1.GetOptions{}) | |
} | |
// mututateDeployment will add environment varables to the container | |
func (f *factory) mututateDeployment(obj *unstructured.Unstructured) error { | |
copyObj := obj.DeepCopy() | |
err := injectEnvsInDeployment(copyObj) | |
if err != nil { | |
return errors.Wrapf(err, "failed to inject %s container into object %s", obj.GetKind()) | |
} | |
// Patch deployment | |
patchBytes, err := getPatchData(obj, copyObj) | |
if err != nil { | |
return errors.Wrapf(err, "failed to get patch bytes") | |
} | |
_, err = f.dynamicClient.Resource(getDeploymentGVR()).Namespace(obj.GetNamespace()).Patch(f.ctx, obj.GetName(), types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}) | |
return err | |
} | |
// injectEnvsInDeployment will inject environment variables to the | |
// given object | |
func injectEnvsInDeployment(obj *unstructured.Unstructured) error { | |
// Since unstructure understands only map[string]interface{} | |
// we also need to inject using map[string]interface | |
// NOTE: All the below lines can be replacable by one line | |
// by convering unstructured into concrete type but here aim | |
// is to deal with unstructured | |
newEnvs := []interface{}{ | |
map[string]interface{}{ | |
"name": "NAMESPACE", | |
"valueFrom": map[string]interface{}{ | |
"fieldRef": map[string]interface{}{ | |
"fieldPath": "metadata.namespace", | |
}, | |
}, | |
}, | |
map[string]interface{}{ | |
"name": "POD_UID", | |
"valueFrom": map[string]interface{}{ | |
"fieldRef": map[string]interface{}{ | |
"fieldPath": "metadata.uid", | |
}, | |
}, | |
}, | |
} | |
conInterface, _, err := unstructured.NestedFieldNoCopy(obj.Object, "spec", "template", "spec", "containers") | |
if err != nil { | |
return errors.Wrapf(err, "failed to get containers") | |
} | |
containers, ok := conInterface.([]interface{}) | |
if !ok { | |
return errors.Errorf("expected of type %T but got %T", []interface{}{}, conInterface) | |
} | |
existingEnvInterface, _, err := unstructured.NestedFieldNoCopy(containers[0].(map[string]interface{}), "env") | |
if err != nil { | |
return errors.Wrapf(err, "failed to get envs present in container") | |
} | |
var updatedEnvs []interface{} | |
if existingEnvInterface != nil { | |
updatedEnvs = append(existingEnvInterface.([]interface{}), newEnvs...) | |
} else { | |
updatedEnvs = newEnvs | |
} | |
return unstructured.SetNestedField(containers[0].(map[string]interface{}), updatedEnvs, "env") | |
} | |
func main() { | |
var kubeconfig *string | |
// flag to get custom kubeconfig | |
kubeconfig = flag.String("kubeconfig", "", "Path for kube config") | |
flag.Parse() | |
// Following lines will get a client to talk to kube-apiserver | |
config, err := getClusterConfig(*kubeconfig) | |
if err != nil { | |
klog.Errorf("failed to get config error: %v", err) | |
return | |
} | |
f := &factory{ | |
ctx: context.Background(), | |
} | |
f, err = f.newDynamicClient(config) | |
if err != nil { | |
klog.Errorf("failed to get dynamic client error: %v", err) | |
return | |
} | |
// Below lines will perform following actions: | |
// 1. Get deployment | |
// 2. Patch the deployment using strategic patch -- it will throw an error | |
unstructObj, err := f.getObject("minio", "minio", getDeploymentGVR()) | |
if err != nil { | |
klog.Errorf("failed to get minio deployment error: %v", err) | |
return | |
} | |
err = f.mututateDeployment(unstructObj) | |
if err != nil { | |
klog.Errorf("failed to mututate deployment error: %v", err) | |
} | |
klog.Infof("Successfully applied patch") | |
} | |
// getPatchData will return difference between original and modified document | |
func getPatchData(originalObj, modifiedObj interface{}) ([]byte, error) { | |
originalData, err := json.Marshal(originalObj) | |
if err != nil { | |
return nil, errors.Wrapf(err, "failed marshal original data") | |
} | |
modifiedData, err := json.Marshal(modifiedObj) | |
if err != nil { | |
return nil, errors.Wrapf(err, "failed marshal original data") | |
} | |
// Using strategicpatch package can cause below error | |
// Error: CreateTwoWayMergePatch failed: unable to find api field in struct Unstructured for the json field "spec" | |
//patchBytes, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, originalObj) | |
// if err != nil { | |
// return nil, errors.Errorf("CreateTwoWayMergePatch failed: %v", err) | |
// } | |
patchBytes, err := jsonpatch.CreateMergePatch(originalData, modifiedData) | |
if err != nil { | |
return nil, errors.Errorf("CreateTwoWayMergePatch failed: %v", err) | |
} | |
return patchBytes, nil | |
} | |
func getDeploymentGVR() schema.GroupVersionResource { | |
return schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment