0

I need to update a secret with specific value,(the secret contain additional data) my question is how I can update just the value and not all the secret data (I don't want to override the existing data). I mean if the secret have additional values I don’t want to override them just the entry foo

updSec := v1.Secret{
    TypeMeta: metav1.TypeMeta{},
    ObjectMeta: metav1.ObjectMeta{
        Name:      "d-values",
        Namespace: "terv”,
    },
    Immutable:  nil,
    Data:       nil,
    StringData: nil,
    Type:       "Opaque",
}
updSec.Data[“foo”] = newVal
if err := r.Client.Update(ctx, &updSec); err != nil {
    return ctrl.Result{}, err
}

The issue is that the secret is already exist and here im creating new object and not sure how to do it right ...I need for secret that called d-values just update the newVal for key foo

update

when trying the code in the answer after I run the

patch, err := yaml.Marshal(updSec) the data looks like following and the patch are failed with error, any idea if its related ?

if I try with the c.Client.Update it works but not with Patch but the Patch is the right way as if I've ties before is should keep them..

enter image description here

tj holwik
  • 163
  • 1
  • 2
  • 8

1 Answers1

2

I don't think you can update a single key using the Update method, but you can certainly do that using Patch instead. Here's an example that uses a StrategicMergePatch; it will replace the key val2 in a secret with the value newval:

package main

import (
  "context"
  "encoding/json"
  "flag"
  "fmt"
  "path/filepath"

  v1 "k8s.io/api/core/v1"
  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  "k8s.io/apimachinery/pkg/types"
  "k8s.io/client-go/kubernetes"
  "k8s.io/client-go/tools/clientcmd"
  "k8s.io/client-go/util/homedir"
)

func main() {
  var kubeconfig *string
  var namespace *string
  var secretname *string

  namespace = flag.String("namespace", "", "namespace of secret")
  secretname = flag.String("name", "", "name of secret")

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

  if *namespace == "" {
    panic(fmt.Errorf("you must specify a namespace"))
  }

  if *secretname == "" {
    panic(fmt.Errorf("you must specify a secret name"))
  }

  config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
  if err != nil {
    panic(err)
  }

  clientset, err := kubernetes.NewForConfig(config)
  if err != nil {
    panic(err)
  }

  secretClient := clientset.CoreV1().Secrets(*namespace)
  ctx := context.TODO()

  updSec := v1.Secret{
    Data: map[string][]byte{
      "val2": []byte("newval"),
    },
  }

  payloadBytes, err := json.Marshal(updSec)
  if err != nil {
    panic(err)
  }

  if _, err = secretClient.Patch(ctx, *secretname,
    types.StrategicMergePatchType, payloadBytes, metav1.PatchOptions{}); err != nil {
    panic(err)
  }

  // Fetch updated secret
  sec, err := secretClient.Get(ctx, *secretname, metav1.GetOptions{})
  if err != nil {
    panic(err)
  }
  secJson, err := json.MarshalIndent(sec, "", "  ")
  if err != nil {
    panic(err)
  }

  fmt.Print(string(secJson))
}

For example, if I create a secret like this:

kubectl create secret generic \
  --from-literal val1=key1 \
  --from-literal val2=key2 example

And then run the above code like this:

go run main.go -namespace default -name example

The code will output the update secret. Looking at the data section, we see:

  "data": {
    "val1": "a2V5MQ==",
    "val2": "bmV3dmFs"
  },

And if we decode val2 we see:

$ kubectl get secret example -o json | jq '.data.val2|@base64d'
"newval"

Using the Operator SDK

If you're working with the Operator SDK, you can use Update if you're first reading the existing value, like this:

  // Read the existing secret
  secret := &corev1.Secret{}
  if err := r.Get(ctx, req.NamespacedName, secret); err != nil {
    panic(err)
  }

  // Check if it needs to be modified
  val, ok := secret.Data["val2"]

  // If yes, update the secret with a new value and then write
  // the entire object back with Update
  if !ok || !bytes.Equal(val, []byte("val2")) {
    ctxlog.Info("needs update", "secret", secret)
    secret.Data["val2"] = []byte("newval")
    if err := r.Update(ctx, secret); err != nil {
      panic(err)
    }
  }

You can use the Patch method if you only want to submit a partial update:

  if !ok || !bytes.Equal(val, []byte("val2")) {
    ctxlog.Info("needs update", "secret", secret)
    newVal := corev1.Secret{
      Data: map[string][]byte{
        "val2": []byte("newval"),
      },
    }

    patch, err := json.Marshal(newVal)
    if err != nil {
      panic(err)
    }

    if err := r.Client.Patch(ctx, secret, client.RawPatch(types.StrategicMergePatchType, patch)); err != nil {
      panic(err)
    }
  }

This is pretty much identical to the earlier example. There are examples of using the client.Patch method in the docs, but I'll be honest, I don't find the example very clear.

larsks
  • 277,717
  • 41
  • 399
  • 399
  • thanks a lot 1+ , this is what I need but the problem is that my code is running inside k8s controller and im using k8s client as it coming from the operator api, is it possible to do the same with k8s client code ? https://sdk.operatorframework.io/docs/building-operators/golang/references/client/#get – tj holwik Oct 23 '22 at 19:15
  • I've updated this answer with examples that work when using the operator sdk. – larsks Oct 23 '22 at 23:00
  • Thanks! I tried it out exactly as you write and I got an error, "http2: client connection lost" but this is not related to the connection as when I change the code to `c.Update` as before it works , any idea? maybe its related to the output of the yaml after running `yaml.Marshal` , please see my post I added there a screen shot, any idea what could be wrong ? – tj holwik Oct 24 '22 at 09:55
  • I'm not sure. The code I wrote works for me; you can see the complete project [here](https://github.com/larsks/secretwatcher). – larsks Oct 24 '22 at 11:28
  • thanks, the secret contain the value as yaml, could this be the issue? I mean `secret.Data["val2"] = yamlValue` – tj holwik Oct 24 '22 at 12:55
  • That shouldn't be an issue, no. YAML is just text, and Kubernetes doesn't care about the content of your secret values. In any case, using Update as I've shown here results in the same changes as using Patch, so if that works you might as well run with it. – larsks Oct 24 '22 at 12:58