// CreateTwoWayMergePatch creates a patch that can be passed to StrategicMergePatch from an original // document and a modified document, which are passed to the method as json encoded content. It will // return a patch that yields the modified document when applied to the original document, or an error // if either of the two documents is invalid. func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, smPatchVersion StrategicMergePatchVersion, fns ...PreconditionFunc) ([]byte, error) { originalMap := map[string]interface{}{} if len(original) > 0 { if err := json.Unmarshal(original, &originalMap); err != nil { return nil, errBadJSONDoc } } modifiedMap := map[string]interface{}{} if len(modified) > 0 { if err := json.Unmarshal(modified, &modifiedMap); err != nil { return nil, errBadJSONDoc } } t, err := getTagStructType(dataStruct) if err != nil { return nil, err } patchMap, err := diffMaps(originalMap, modifiedMap, t, false, false, smPatchVersion) if err != nil { return nil, err } // Apply the preconditions to the patch, and return an error if any of them fail. for _, fn := range fns { if !fn(patchMap) { return nil, newErrPreconditionFailed(patchMap) } } return json.Marshal(patchMap) }
func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error { type decodeList struct { Items []gojson.RawMessage } var dList decodeList if err := json.Unmarshal(data, &dList); err != nil { return err } if err := json.Unmarshal(data, &list.Object); err != nil { return err } delete(list.Object, "items") list.Items = nil for _, i := range dList.Items { unstruct := &Unstructured{} if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil { return err } list.Items = append(list.Items, unstruct) } return nil }
// StrategicMergePatch applies a strategic merge patch. The patch and the original document // must be json encoded content. A patch can be created from an original and a modified document // by calling CreateStrategicMergePatch. func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte, error) { if original == nil { original = []byte("{}") } if patch == nil { patch = []byte("{}") } originalMap := map[string]interface{}{} err := json.Unmarshal(original, &originalMap) if err != nil { return nil, errBadJSONDoc } patchMap := map[string]interface{}{} err = json.Unmarshal(patch, &patchMap) if err != nil { return nil, errBadJSONDoc } t, err := getTagStructType(dataStruct) if err != nil { return nil, err } result, err := mergeMap(originalMap, patchMap, t) if err != nil { return nil, err } return json.Marshal(result) }
// CreateThreeWayMergePatch reconciles a modified configuration with an original configuration, // while preserving any changes or deletions made to the original configuration in the interim, // and not overridden by the current configuration. All three documents must be passed to the // method as json encoded content. It will return a strategic merge patch, or an error if any // of the documents is invalid, or if there are any preconditions that fail against the modified // configuration, or, if force is false and there are conflicts between the modified and current // configurations. func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct interface{}, force bool, fns ...PreconditionFunc) ([]byte, error) { originalMap := map[string]interface{}{} if len(original) > 0 { if err := json.Unmarshal(original, &originalMap); err != nil { return nil, errBadJSONDoc } } modifiedMap := map[string]interface{}{} if len(modified) > 0 { if err := json.Unmarshal(modified, &modifiedMap); err != nil { return nil, errBadJSONDoc } } currentMap := map[string]interface{}{} if len(current) > 0 { if err := json.Unmarshal(current, ¤tMap); err != nil { return nil, errBadJSONDoc } } t, err := getTagStructType(dataStruct) if err != nil { return nil, err } // The patch is the difference from current to modified without deletions, plus deletions // from original to modified. To find it, we compute deletions, which are the deletions from // original to modified, and delta, which is the difference from current to modified without // deletions, and then apply delta to deletions as a patch, which should be strictly additive. deltaMap, err := diffMaps(currentMap, modifiedMap, t, false, true) if err != nil { return nil, err } deletionsMap, err := diffMaps(originalMap, modifiedMap, t, true, false) if err != nil { return nil, err } patchMap, err := mergeMap(deletionsMap, deltaMap, t) if err != nil { return nil, err } // Apply the preconditions to the patch, and return an error if any of them fail. for _, fn := range fns { if !fn(patchMap) { return nil, newErrPreconditionFailed(patchMap) } } // TODO(jackgr): If force is false, and the patch contains any keys that are also in current, // then return a conflict error. return json.Marshal(patchMap) }
func (unstructuredJSONScheme) DecodeInto(data []byte, obj Object) error { unstruct, ok := obj.(*Unstructured) if !ok { return fmt.Errorf("the unstructured JSON scheme does not recognize %v", reflect.TypeOf(obj)) } m := make(map[string]interface{}) if err := json.Unmarshal(data, &m); err != nil { return err } if v, ok := m["kind"]; ok { if s, ok := v.(string); ok { unstruct.Kind = s } } if v, ok := m["apiVersion"]; ok { if s, ok := v.(string); ok { unstruct.APIVersion = s } } if len(unstruct.APIVersion) == 0 { return conversion.NewMissingVersionErr(string(data)) } if len(unstruct.Kind) == 0 { return conversion.NewMissingKindErr(string(data)) } unstruct.Object = m return nil }
// Since GCL API is not easily available from the outside of cluster // we use gcloud command to perform search with filter func readFilteredEntriesFromGcl(filter string) ([]string, error) { framework.Logf("Reading entries from GCL with filter '%v'", filter) argList := []string{"beta", "logging", "read", filter, "--format", "json", "--project", framework.TestContext.CloudConfig.ProjectID, } output, err := exec.Command("gcloud", argList...).CombinedOutput() if err != nil { return nil, err } var entries []*LogEntry if err = json.Unmarshal(output, &entries); err != nil { return nil, err } framework.Logf("Read %d entries from GCL", len(entries)) var result []string for _, entry := range entries { if entry.TextPayload != "" { result = append(result, entry.TextPayload) } } return result, nil }
func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error { m := make(map[string]interface{}) if err := json.Unmarshal(data, &m); err != nil { return err } if v, ok := m["kind"]; ok { if s, ok := v.(string); ok { unstruct.Kind = s } } if v, ok := m["apiVersion"]; ok { if s, ok := v.(string); ok { unstruct.APIVersion = s } } if metadata, ok := m["metadata"]; ok { if metadata, ok := metadata.(map[string]interface{}); ok { if name, ok := metadata["name"]; ok { if name, ok := name.(string); ok { unstruct.Name = name } } } } unstruct.Object = m return nil }
func (s unstructuredJSONScheme) decodeInto(data []byte, obj Object) error { switch x := obj.(type) { case *Unstructured: return s.decodeToUnstructured(data, x) case *UnstructuredList: return s.decodeToList(data, x) default: return json.Unmarshal(data, x) } }
func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error { m := make(map[string]interface{}) if err := json.Unmarshal(data, &m); err != nil { return err } unstruct.Object = m return nil }
func (unstructuredJSONScheme) DataVersionAndKind(data []byte) (version, kind string, err error) { obj := TypeMeta{} if err := json.Unmarshal(data, &obj); err != nil { return "", "", err } if len(obj.APIVersion) == 0 { return "", "", conversion.NewMissingVersionErr(string(data)) } if len(obj.Kind) == 0 { return "", "", conversion.NewMissingKindErr(string(data)) } return obj.APIVersion, obj.Kind, nil }
// This function takes a JSON map and sorts all the lists that should be merged // by key. This is needed by tests because in JSON, list order is significant, // but in Strategic Merge Patch, merge lists do not have significant order. // Sorting the lists allows for order-insensitive comparison of patched maps. func sortMergeListsByName(mapJSON []byte, dataStruct interface{}) ([]byte, error) { var m map[string]interface{} err := json.Unmarshal(mapJSON, &m) if err != nil { return nil, err } newM, err := sortMergeListsByNameMap(m, reflect.TypeOf(dataStruct)) if err != nil { return nil, err } return json.Marshal(newM) }
func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error { type decodeList struct { Items []gojson.RawMessage } var dList decodeList if err := json.Unmarshal(data, &dList); err != nil { return err } if err := json.Unmarshal(data, &list.Object); err != nil { return err } // For typed lists, e.g., a PodList, API server doesn't set each item's // APIVersion and Kind. We need to set it. listAPIVersion := list.GetAPIVersion() listKind := list.GetKind() itemKind := strings.TrimSuffix(listKind, "List") delete(list.Object, "items") list.Items = nil for _, i := range dList.Items { unstruct := &Unstructured{} if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil { return err } // This is hacky. Set the item's Kind and APIVersion to those inferred // from the List. if len(unstruct.GetKind()) == 0 && len(unstruct.GetAPIVersion()) == 0 { unstruct.SetKind(itemKind) unstruct.SetAPIVersion(listAPIVersion) } list.Items = append(list.Items, unstruct) } return nil }
func (s unstructuredJSONScheme) decodeInto(data []byte, obj Object) error { switch x := obj.(type) { case *Unstructured: return s.decodeToUnstructured(data, x) case *UnstructuredList: return s.decodeToList(data, x) case *VersionedObjects: o, err := s.decode(data) if err == nil { x.Objects = []Object{o} } return err default: return json.Unmarshal(data, x) } }
func (unstructuredJSONScheme) DataKind(data []byte) (unversioned.GroupVersionKind, error) { obj := TypeMeta{} if err := json.Unmarshal(data, &obj); err != nil { return unversioned.GroupVersionKind{}, err } if len(obj.APIVersion) == 0 { return unversioned.GroupVersionKind{}, conversion.NewMissingVersionErr(string(data)) } if len(obj.Kind) == 0 { return unversioned.GroupVersionKind{}, conversion.NewMissingKindErr(string(data)) } gv, err := unversioned.ParseGroupVersion(obj.APIVersion) if err != nil { return unversioned.GroupVersionKind{}, err } return gv.WithKind(obj.Kind), nil }
func (s unstructuredJSONScheme) decode(data []byte) (Object, error) { type detector struct { Items gojson.RawMessage } var det detector if err := json.Unmarshal(data, &det); err != nil { return nil, err } if det.Items != nil { list := &UnstructuredList{} err := s.decodeToList(data, list) return list, err } // No Items field, so it wasn't a list. unstruct := &Unstructured{} err := s.decodeToUnstructured(data, unstruct) return unstruct, err }
func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error { type decodeList struct { TypeMeta `json:",inline"` Items []gojson.RawMessage } var dList decodeList if err := json.Unmarshal(data, &dList); err != nil { return err } list.TypeMeta = dList.TypeMeta list.Items = nil for _, i := range dList.Items { unstruct := &Unstructured{} if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil { return err } list.Items = append(list.Items, unstruct) } return nil }
// CreateThreeWayMergePatch reconciles a modified configuration with an original configuration, // while preserving any changes or deletions made to the original configuration in the interim, // and not overridden by the current configuration. All three documents must be passed to the // method as json encoded content. It will return a strategic merge patch, or an error if any // of the documents is invalid, or if there are any preconditions that fail against the modified // configuration, or, if overwrite is false and there are conflicts between the modified and current // configurations. Conflicts are defined as keys changed differently from original to modified // than from original to current. In other words, a conflict occurs if modified changes any key // in a way that is different from how it is changed in current (e.g., deleting it, changing its // value). func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct interface{}, overwrite bool, smPatchVersion StrategicMergePatchVersion, fns ...PreconditionFunc) ([]byte, error) { originalMap := map[string]interface{}{} if len(original) > 0 { if err := json.Unmarshal(original, &originalMap); err != nil { return nil, errBadJSONDoc } } modifiedMap := map[string]interface{}{} if len(modified) > 0 { if err := json.Unmarshal(modified, &modifiedMap); err != nil { return nil, errBadJSONDoc } } currentMap := map[string]interface{}{} if len(current) > 0 { if err := json.Unmarshal(current, ¤tMap); err != nil { return nil, errBadJSONDoc } } t, err := getTagStructType(dataStruct) if err != nil { return nil, err } // The patch is the difference from current to modified without deletions, plus deletions // from original to modified. To find it, we compute deletions, which are the deletions from // original to modified, and delta, which is the difference from current to modified without // deletions, and then apply delta to deletions as a patch, which should be strictly additive. deltaMap, err := diffMaps(currentMap, modifiedMap, t, false, true, smPatchVersion) if err != nil { return nil, err } deletionsMap, err := diffMaps(originalMap, modifiedMap, t, true, false, smPatchVersion) if err != nil { return nil, err } patchMap, err := mergeMap(deletionsMap, deltaMap, t) if err != nil { return nil, err } // Apply the preconditions to the patch, and return an error if any of them fail. for _, fn := range fns { if !fn(patchMap) { return nil, newErrPreconditionFailed(patchMap) } } // If overwrite is false, and the patch contains any keys that were changed differently, // then return a conflict error. if !overwrite { changedMap, err := diffMaps(originalMap, currentMap, t, false, false, smPatchVersion) if err != nil { return nil, err } hasConflicts, err := MergingMapsHaveConflicts(patchMap, changedMap, dataStruct) if err != nil { return nil, err } if hasConflicts { return nil, newErrConflict(toYAMLOrError(patchMap), toYAMLOrError(changedMap)) } } return json.Marshal(patchMap) }