Search code examples
gokubernetesserializationyamlclient-go

Exclude "Status" from serialized output of Kubernetes objects


I would like to serialize a Subscription resource (from github.com/operator-framework/api/pkg/operators/v1alpha1) to YAML using code like this:

    subscription := operatorsv1alpha1.Subscription{
        TypeMeta: metav1.TypeMeta{
            APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion,
            Kind:       operatorsv1alpha1.SubscriptionKind,
        },
        ObjectMeta: metav1.ObjectMeta{
            Namespace: namespaceName,
            Name:      pkg.Name,
        },
        Spec: &operatorsv1alpha1.SubscriptionSpec{
            Package:                pkg.Name,
            Channel:                channel.Name,
            InstallPlanApproval:    operatorsv1alpha1.Approval(subscribeFlags.Approval),
            CatalogSource:          pkg.Status.CatalogSource,
            CatalogSourceNamespace: pkg.Status.CatalogSourceNamespace,
        },
    }

    operatorsv1alpha1.AddToScheme(scheme.Scheme)
    corev1.AddToScheme(scheme.Scheme)

    serializer := json.NewSerializerWithOptions(
        json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme,
        json.SerializerOptions{
            Pretty: true,
            Yaml:   true,
            Strict: true,
        })

    if err := serializer.Encode(&subscription, os.Stdout); err != nil {
        return err
    }

This works, except that operator-framework/api defines a Subcription resource like this:

type Subscription struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata"`

    Spec *SubscriptionSpec `json:"spec"`
    // +optional
    Status SubscriptionStatus `json:"status"`
}

Which means that the serialized output always includes a status element with a null status.lastUpdated field:

apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  creationTimestamp: null
  name: argocd-operator
spec:
  channel: alpha
  installPlanApproval: Automatic
  name: argocd-operator
  source: operatorhubio-catalog
  sourceNamespace: olm
status:
  lastUpdated: null

Submitting the serialized manifest fails with:

error: error validating "STDIN": error validating data: ValidationError(Subscription.status): missing required field "lastUpdated" in com.coreos.operators.v1alpha1.Subscription.status; if you choose to ignore these errors, turn validation off with --validate=false

Is there a canonical method for serializing these resources without including the status field?


Solution

  • It is not possible with the "k8s.io/apimachinery/pkg/runtime/serializer/json" only. One way to achieve this is to serialize runtime object to map[string]interface{}{} and use "transformers" to modify the map and only then write it to file.

    Transformer from the kubernetes-sigs/controller-tools (link above):

    package main
    
    import (
        "bytes"
        "fmt"
    
        "gopkg.in/yaml.v2"
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime/serializer/json"
    )
    
    // TransformRemoveCreationTimestamp ensures we do not write the metadata.creationTimestamp field.
    func TransformRemoveCreationTimestamp(obj map[string]interface{}) error {
        metadata, ok := obj["metadata"].(map[interface{}]interface{})
        if !ok {
            return nil
        }
        delete(metadata, "creationTimestamp")
        return nil
    }
    
    func TransformRemoveStatus(obj map[string]interface{}) error {
        _, ok := obj["status"].(map[interface{}]interface{})
        if !ok {
            return nil
        }
        delete(obj, "status")
        return nil
    }
    
    
    func main() {
        pod := corev1.Pod{
            ObjectMeta: metav1.ObjectMeta{
                Name:      "pod-1",
                Namespace: "default",
            },
            Spec: corev1.PodSpec{
                Containers: []corev1.Container{
                    {
                        Name:  "nginx",
                        Image: "nginx:4.9.2",
                        Ports: []corev1.ContainerPort{
                            {
                                ContainerPort: 80,
                            },
                        },
                    },
                },
            },
        }
        // serialize k8s object to yaml
        e := json.NewYAMLSerializer(json.DefaultMetaFactory, nil, nil)
        b := bytes.NewBufferString("")
        err := e.Encode(&pod, b)
        if err != nil {
            panic(err)
        }
    
        // deserialize yaml to golang map
        obj := map[string]interface{}{}
        err = yaml.Unmarshal(b.Bytes(), &obj)
        if err != nil {
            panic(err)
        }
    
        // transform
        err = TransformRemoveCreationTimestamp(obj)
        if err != nil {
            panic(err)
        }
        err = TransformRemoveStatus(obj)
        if err != nil {
            panic(err)
        }
    
        // serialize back to yaml
        s, err := yaml.Marshal(obj)
        if err != nil {
            panic(err)
        }
        fmt.Printf("%s", s)
    }