The answer is really trivial. The original code assumes that namespace
is provided in the manifest. The deployment endpoint does not automatically set namespace to default
if the provided namespace is "", and errors out because "" is not a valid namespace. Therefore, I added logic to set namespace to default
if not provided and presto, the server side apply will create the resource if it doesn't exist and update if it does exist. Thanks again @ymmt2005 .
package main
import (
...
)
const resourceYAML = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: mike-nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: 'nginx:latest'
ports:
- containerPort: 80
`
func main() {
ctx := context.Background()
// Create dynamic discovery client from local kubeconfig file
kubePath := filepath.Join(homedir.HomeDir(), ".kube", "config")
cfg, err := clientcmd.BuildConfigFromFlags("", kubePath)
if err != nil {
log.Fatalf("error building config, %v
", err)
}
dynClient, err := dynamic.NewForConfig(cfg)
if err != nil {
log.Fatalf("error creating client, %v
", err)
}
disClient, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
log.Fatalf("error creating discovery client, %v
", err)
}
// Decode YAML manifest & get GVK
decodeUnstr := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
obj := &unstructured.Unstructured{}
_, gvk, err := decodeUnstr.Decode([]byte(resourceYAML), nil, obj)
if err != nil {
log.Fatalf("error decoding manifest, %v
", err)
}
jsonObj, err := json.Marshal(obj)
if err != nil {
log.Fatalf("error marshaling object, %v
", err)
}
// Find GVR using GVK
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(disClient))
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
log.Fatalf("error finding GVR, %v
", err)
}
// Set Namespace to default if not provided in manifest
var ns string
if ns = obj.GetNamespace(); ns == "" {
ns = "default"
}
// Get REST interface for the GVR, checking for namespace or cluster-wide
var dr dynamic.ResourceInterface
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
// Namespaced resource
dr = dynClient.Resource(mapping.Resource).Namespace(ns)
} else {
// Cluster-wide resource
dr = dynClient.Resource(mapping.Resource)
}
// Create or Update the object with SSA
options := metav1.PatchOptions{FieldManager: "sample-controller"}
_, err = dr.Patch(ctx, obj.GetName(), types.ApplyPatchType, jsonObj, options)
if err != nil {
log.Fatalf("error patching, %v
", err)
}
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…