|  | 
|  | 1 | +// Copyright 2021 - 2022 Crunchy Data Solutions, Inc. | 
|  | 2 | +// | 
|  | 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 4 | +// you may not use this file except in compliance with the License. | 
|  | 5 | +// You may obtain a copy of the License at | 
|  | 6 | +// | 
|  | 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 8 | +// | 
|  | 9 | +// Unless required by applicable law or agreed to in writing, software | 
|  | 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 12 | +// See the License for the specific language governing permissions and | 
|  | 13 | +// limitations under the License. | 
|  | 14 | + | 
|  | 15 | +package pgupgrade | 
|  | 16 | + | 
|  | 17 | +import ( | 
|  | 18 | +"context" | 
|  | 19 | +"encoding/json" | 
|  | 20 | +"reflect" | 
|  | 21 | +"strings" | 
|  | 22 | + | 
|  | 23 | +"k8s.io/apimachinery/pkg/types" | 
|  | 24 | +"sigs.k8s.io/controller-runtime/pkg/client" | 
|  | 25 | +) | 
|  | 26 | + | 
|  | 27 | +// JSON6902 represents a JSON Patch according to RFC 6902; the same as | 
|  | 28 | +// k8s.io/apimachinery/pkg/types.JSONPatchType. | 
|  | 29 | +type JSON6902 []interface{} | 
|  | 30 | + | 
|  | 31 | +// NewJSONPatch creates a new JSON Patch according to RFC 6902; the same as | 
|  | 32 | +// k8s.io/apimachinery/pkg/types.JSONPatchType. | 
|  | 33 | +func NewJSONPatch() *JSON6902 { return &JSON6902{} } | 
|  | 34 | + | 
|  | 35 | +// escapeJSONPointer encodes '~' and '/' according to RFC 6901. | 
|  | 36 | +var escapeJSONPointer = strings.NewReplacer( | 
|  | 37 | +"~", "~0", | 
|  | 38 | +"/", "~1", | 
|  | 39 | +).Replace | 
|  | 40 | + | 
|  | 41 | +func (*JSON6902) pointer(tokens ...string) string { | 
|  | 42 | +var b strings.Builder | 
|  | 43 | + | 
|  | 44 | +for _, t := range tokens { | 
|  | 45 | +_ = b.WriteByte('/') | 
|  | 46 | +_, _ = b.WriteString(escapeJSONPointer(t)) | 
|  | 47 | +} | 
|  | 48 | + | 
|  | 49 | +return b.String() | 
|  | 50 | +} | 
|  | 51 | + | 
|  | 52 | +// Add appends an "add" operation to patch. | 
|  | 53 | +// | 
|  | 54 | +// > The "add" operation performs one of the following functions, | 
|  | 55 | +// > depending upon what the target location references: | 
|  | 56 | +// > | 
|  | 57 | +// > o If the target location specifies an array index, a new value is | 
|  | 58 | +// > inserted into the array at the specified index. | 
|  | 59 | +// > | 
|  | 60 | +// > o If the target location specifies an object member that does not | 
|  | 61 | +// > already exist, a new member is added to the object. | 
|  | 62 | +// > | 
|  | 63 | +// > o If the target location specifies an object member that does exist, | 
|  | 64 | +// > that member's value is replaced. | 
|  | 65 | +// | 
|  | 66 | + | 
|  | 67 | +func (patch *JSON6902) Add(path ...string) func(value interface{}) *JSON6902 { | 
|  | 68 | +i := len(*patch) | 
|  | 69 | +f := func(value interface{}) *JSON6902 { | 
|  | 70 | +(*patch)[i] = map[string]interface{}{ | 
|  | 71 | +"op": "add", | 
|  | 72 | +"path": patch.pointer(path...), | 
|  | 73 | +"value": value, | 
|  | 74 | +} | 
|  | 75 | +return patch | 
|  | 76 | +} | 
|  | 77 | + | 
|  | 78 | +*patch = append(*patch, f) | 
|  | 79 | + | 
|  | 80 | +return f | 
|  | 81 | +} | 
|  | 82 | + | 
|  | 83 | +// Remove appends a "remove" operation to patch. | 
|  | 84 | +// | 
|  | 85 | +// > The "remove" operation removes the value at the target location. | 
|  | 86 | +// > | 
|  | 87 | +// > The target location MUST exist for the operation to be successful. | 
|  | 88 | +// | 
|  | 89 | + | 
|  | 90 | +func (patch *JSON6902) Remove(path ...string) *JSON6902 { | 
|  | 91 | +*patch = append(*patch, map[string]interface{}{ | 
|  | 92 | +"op": "remove", | 
|  | 93 | +"path": patch.pointer(path...), | 
|  | 94 | +}) | 
|  | 95 | + | 
|  | 96 | +return patch | 
|  | 97 | +} | 
|  | 98 | + | 
|  | 99 | +// Replace appends a "replace" operation to patch. | 
|  | 100 | +// | 
|  | 101 | +// > The "replace" operation replaces the value at the target location | 
|  | 102 | +// > with a new value. | 
|  | 103 | +// > | 
|  | 104 | +// > The target location MUST exist for the operation to be successful. | 
|  | 105 | +// | 
|  | 106 | + | 
|  | 107 | +func (patch *JSON6902) Replace(path ...string) func(value interface{}) *JSON6902 { | 
|  | 108 | +i := len(*patch) | 
|  | 109 | +f := func(value interface{}) *JSON6902 { | 
|  | 110 | +(*patch)[i] = map[string]interface{}{ | 
|  | 111 | +"op": "replace", | 
|  | 112 | +"path": patch.pointer(path...), | 
|  | 113 | +"value": value, | 
|  | 114 | +} | 
|  | 115 | +return patch | 
|  | 116 | +} | 
|  | 117 | + | 
|  | 118 | +*patch = append(*patch, f) | 
|  | 119 | + | 
|  | 120 | +return f | 
|  | 121 | +} | 
|  | 122 | + | 
|  | 123 | +// Bytes returns the JSON representation of patch. | 
|  | 124 | +func (patch JSON6902) Bytes() ([]byte, error) { return patch.Data(nil) } | 
|  | 125 | + | 
|  | 126 | +// Data returns the JSON representation of patch. | 
|  | 127 | +func (patch JSON6902) Data(client.Object) ([]byte, error) { return json.Marshal(patch) } | 
|  | 128 | + | 
|  | 129 | +// IsEmpty returns true when patch has no operations. | 
|  | 130 | +func (patch JSON6902) IsEmpty() bool { return len(patch) == 0 } | 
|  | 131 | + | 
|  | 132 | +// Type returns k8s.io/apimachinery/pkg/types.JSONPatchType. | 
|  | 133 | +func (patch JSON6902) Type() types.PatchType { return types.JSONPatchType } | 
|  | 134 | + | 
|  | 135 | +// patch sends patch to object's endpoint in the Kubernetes API and updates | 
|  | 136 | +// object with any returned content. The fieldManager is set to r.Owner, but | 
|  | 137 | +// can be overridden in options. | 
|  | 138 | +// - https://docs.k8s.io/reference/using-api/server-side-apply/#managers | 
|  | 139 | +func (r *PGUpgradeReconciler) patch( | 
|  | 140 | +ctx context.Context, object client.Object, | 
|  | 141 | +patch client.Patch, options ...client.PatchOption, | 
|  | 142 | +) error { | 
|  | 143 | +options = append([]client.PatchOption{r.Owner}, options...) | 
|  | 144 | +return r.Client.Patch(ctx, object, patch, options...) | 
|  | 145 | +} | 
|  | 146 | + | 
|  | 147 | +// apply sends an apply patch to object's endpoint in the Kubernetes API and | 
|  | 148 | +// updates object with any returned content. The fieldManager is set to | 
|  | 149 | +// r.Owner and the force parameter is true. | 
|  | 150 | +// - https://docs.k8s.io/reference/using-api/server-side-apply/#managers | 
|  | 151 | +// - https://docs.k8s.io/reference/using-api/server-side-apply/#conflicts | 
|  | 152 | +func (r *PGUpgradeReconciler) apply(ctx context.Context, object client.Object) error { | 
|  | 153 | +// Generate an apply-patch by comparing the object to its zero value. | 
|  | 154 | +zero := reflect.New(reflect.TypeOf(object).Elem()).Interface() | 
|  | 155 | +data, err := client.MergeFrom(zero.(client.Object)).Data(object) | 
|  | 156 | +apply := client.RawPatch(client.Apply.Type(), data) | 
|  | 157 | + | 
|  | 158 | +// Send the apply-patch with force=true. | 
|  | 159 | +if err == nil { | 
|  | 160 | +err = r.patch(ctx, object, apply, client.ForceOwnership) | 
|  | 161 | +} | 
|  | 162 | + | 
|  | 163 | +return err | 
|  | 164 | +} | 
0 commit comments