package unstructured import ( "bytes" "encoding/json" "strings" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" ) const YAMLFileDelimiter = "\n---\n" // Aliases for upstream unstructured package types. type Unstructured = unstructured.Unstructured var ( // ErrUnstructuredFieldNotFound error returned when field not found in unstructured obj. ErrUnstructuredFieldNotFound = errors.New("field not found") ) // New creates an empty unstructured object with the provided type meta func New(gv schema.GroupVersion, kind, namespace, name string) *unstructured.Unstructured { u := &unstructured.Unstructured{} u.SetNamespace(namespace) u.SetName(name) u.SetGroupVersionKind(schema.GroupVersionKind{ Group: gv.Group, Kind: kind, Version: gv.Version, }) return u } // FromUnstructured converts from Unstructured to a concrete K8s type func FromUnstructured(u *unstructured.Unstructured, obj interface{}) (err error) { err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj) return err } // ToUnstructured converts a generic K8s object into Unstructured. Typically // useful for working with libraries that accept []*unstructured.Unstructured func ToUnstructured(obj client.Object) (*unstructured.Unstructured, error) { return FromRuntime(obj) } func FromRuntime(obj runtime.Object) (*unstructured.Unstructured, error) { // If the incoming object is already unstructured, perform a deep copy first // otherwise DefaultUnstructuredConverter ends up returning the inner map without // making a copy. if _, ok := obj.(runtime.Unstructured); ok { obj = obj.DeepCopyObject() } rawMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) if err != nil { return nil, err } return &unstructured.Unstructured{Object: rawMap}, nil } // ToUnstructuredArray converts one or more K8s objects into an array of Unstructured // objects. Useful for working with libraries that accept []*unstructured.Unstructured func ToUnstructuredArray(objs ...client.Object) ([]*unstructured.Unstructured, error) { var uu []*unstructured.Unstructured for _, o := range objs { u, err := ToUnstructured(o) if err != nil { return nil, err } uu = append(uu, u) } return uu, nil } // ToClientObjArray converts one ore more unstructured objects into an array // of client.Object by casting. func ToClientObjArray(objs ...*unstructured.Unstructured) []client.Object { clobjs := make([]client.Object, len(objs)) for i, obj := range objs { clobjs[i] = obj } return clobjs } // ToUnstructuredList converts a K8s object to Unstructured and then adds it to // an UnstructuredList before returning it func ToUnstructuredList(obj client.Object) (*unstructured.UnstructuredList, error) { un, err := ToUnstructured(obj) if err != nil { return nil, err } unl := &unstructured.UnstructuredList{Items: []unstructured.Unstructured{*un}} unl.SetAPIVersion("v1") unl.SetKind("List") return unl, nil } // AddToUnstructuredListBytes takes in an UnstructuredList represented as []bytes // and appends the provided client.Object to it. The result is seralized back // into []byte and returned. func AddToUnstructuredListBytes(resources []byte, obj client.Object) ([]byte, error) { if len(resources) == 0 { unl, err := ToUnstructuredList(obj) if err != nil { return nil, err } return unl.MarshalJSON() } unl := &unstructured.UnstructuredList{} if err := unl.UnmarshalJSON(resources); err != nil { return nil, err } un, err := ToUnstructured(obj) if err != nil { return nil, err } unl.Items = append(unl.Items, *un) return unl.MarshalJSON() } // StringsToUnstructuredList converts an array of strings into an UnstructuredList func StringsToUnstructuredList(objs []string) (*unstructured.UnstructuredList, error) { resp := &unstructured.UnstructuredList{} for _, obj := range objs { resource := &unstructured.Unstructured{} if err := resource.UnmarshalJSON([]byte(obj)); err != nil { return resp, err } resp.Items = append(resp.Items, *resource) } return resp, nil } // UnstructuredListToStrings converts an UnstructuredList into an array of strings // where each element is an item from the UnstructuredList // //nolint:revive // UnstructuredListToStrings is more clear than ListToStrings func UnstructuredListToStrings(unl *unstructured.UnstructuredList) ([]string, error) { if unl == nil { return []string{}, nil } var result []string for _, item := range unl.Items { data, err := item.MarshalJSON() if err != nil { return result, err } result = append(result, string(data)) } return result, nil } // UnmarshalField is a wrapper around JSON and Unstructured objects to decode and copy a specific field // value into an object. func UnmarshalField(u *unstructured.Unstructured, v interface{}, fields ...string) error { value, found, err := unstructured.NestedFieldNoCopy(u.Object, fields...) if err != nil { return errors.Wrapf(err, "failed to retrieve field %q from %q", strings.Join(fields, "."), u.GroupVersionKind()) } if !found || value == nil { return ErrUnstructuredFieldNotFound } valueBytes, err := json.Marshal(value) if err != nil { return errors.Wrapf(err, "failed to json-encode field %q value from %q", strings.Join(fields, "."), u.GroupVersionKind()) } if err := json.Unmarshal(valueBytes, v); err != nil { return errors.Wrapf(err, "failed to json-decode field %q value from %q", strings.Join(fields, "."), u.GroupVersionKind()) } return nil } // ToJSON converts unstructured objects to multi doc json bytes func ToJSON(uobjs []*unstructured.Unstructured) ([]byte, error) { jsonDoc := make([]byte, 0) for _, doc := range uobjs { jsonData, err := doc.MarshalJSON() if err != nil { return nil, err } jsonDoc = append(jsonDoc, jsonData...) } return jsonDoc, nil } // FromJSON converts a json multi doc bytes to unstructured object list func FromJSON(jsonData []byte) ([]*unstructured.Unstructured, error) { manifests := []*unstructured.Unstructured{} decoder := json.NewDecoder(bytes.NewBuffer(jsonData)) for { var rawObj runtime.RawExtension if err := decoder.Decode(&rawObj); err != nil { break } obj := &unstructured.Unstructured{} if rawObj.Size() == 0 { continue } if err := obj.UnmarshalJSON(rawObj.Raw); err != nil { return nil, err } manifests = append(manifests, obj) } return manifests, nil }