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