Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
202 views
in Technique[技术] by (71.8m points)

kubernetes - Resource not found error performing SSA create using dynamic client

I was following @ymmt2005 excellent dynamic client guide. All is good until the final step when I make the actual PATCH call, and I get a the server could not find the requested resource error. Just about everything seems right, except I'm unsure about the 'FieldManager' field in the PathOptions struct. I'm not sure what "the actor or entity that is making these changes" refers to. Does this need to match something in my code or system? Any other ideas?

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)
    }

    // 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(obj.GetNamespace())
    } 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)
    }
}

[edit] I confirmed that I was only able to use 'Patch' on a resource that already existed. I tweaked the code to use 'Create' to create the resource, then I was able to successfully do a 'Patch' against it to change. To overcome the FieldManager inconsistencies I added Force=true to the PatchOptions which is recommended in the docs anyway. I'd still like to know how I can create if resource doesn't exist and update if it does--maybe just test for exist?

question from:https://stackoverflow.com/questions/65516708/resource-not-found-error-performing-ssa-create-using-dynamic-client

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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)
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...