Skip to content

Commit 3b8aaf1

Browse files
authored
Use Patch objects for small updates
JSON Merge Patch cannot describe partial changes to JSON arrays, and CustomResources are never going to support Strategic Merge Patch (https://issue.k8s.io/58414). In preparation for more deeply structured CustomResources, we should get into the habit of using JSON Patches. This commit introduces two new types, `kubeapi.JSON6902` and `kubeapi.Merge7386`, to represent entire JSON Patch and JSON Merge Patch documents. The former escapes individual tokens of the operation paths, making it easier and safer to use than the existing `util.JSONPatchOperation` type. If we ever start using the `sigs.k8s.io/controller-runtime/pkg/client` package, these types are positioned to implement its `Patch` interface. Additionally, using these new types eliminates our direct dependency on `github.com/evanphx/json-patch` which has been a Go module since v4.6.0 and should be imported as `github.com/evanphx/json-patch/v4`. This reduces some of the effort needed to switch to Go modules. Issue: [ch9453]
1 parent 4d6c31a commit 3b8aaf1

File tree

28 files changed

+665
-625
lines changed

28 files changed

+665
-625
lines changed

Gopkg.lock

Lines changed: 0 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ required = [
1111
"k8s.io/code-generator",
1212
]
1313

14-
[[constraint]]
15-
name = "github.com/evanphx/json-patch"
16-
version = "4.6.0"
17-
1814
[[constraint]]
1915
name = "github.com/fatih/color"
2016
version = "1.9.0"

internal/apiserver/labelservice/labelimpl.go

Lines changed: 24 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,16 @@ limitations under the License.
1616
*/
1717

1818
import (
19-
"encoding/json"
2019
"errors"
2120
"strings"
2221

2322
"github.com/crunchydata/postgres-operator/internal/apiserver"
2423
"github.com/crunchydata/postgres-operator/internal/config"
24+
"github.com/crunchydata/postgres-operator/internal/kubeapi"
2525
crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1"
2626
msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs"
2727
"github.com/crunchydata/postgres-operator/pkg/events"
28-
jsonpatch "github.com/evanphx/json-patch"
2928
log "github.com/sirupsen/logrus"
30-
v1 "k8s.io/api/apps/v1"
31-
"k8s.io/apimachinery/pkg/api/meta"
3229
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3330
"k8s.io/apimachinery/pkg/types"
3431
"k8s.io/apimachinery/pkg/util/validation"
@@ -117,12 +114,18 @@ func Label(request *msgs.LabelRequest, ns, pgouser string) msgs.LabelResponse {
117114
}
118115

119116
func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLabels map[string]string, ns, pgouser string) {
117+
patchBytes, err := kubeapi.NewMergePatch().Add("metadata", "labels")(newLabels).Bytes()
118+
if err != nil {
119+
log.Error(err.Error())
120+
return
121+
}
122+
120123
for i := 0; i < len(items); i++ {
121124
if DryRun {
122125
log.Debug("dry run only")
123126
} else {
124-
log.Debugf("adding label to cluster %s", items[i].Spec.Name)
125-
err := PatchPgcluster(newLabels, items[i], ns)
127+
log.Debugf("patching cluster %s: %s", items[i].Spec.Name, patchBytes)
128+
_, err := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Patch(items[i].Spec.Name, types.MergePatchType, patchBytes)
126129
if err != nil {
127130
log.Error(err.Error())
128131
}
@@ -163,8 +166,8 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab
163166
for _, d := range deployments.Items {
164167
//update Deployment with the label
165168
if !DryRun {
166-
//err := updateLabels(&d, items[i].Spec.Name, newLabels)
167-
err := updateLabels(&d, d.Name, newLabels, ns)
169+
log.Debugf("patching deployment %s: %s", d.Name, patchBytes)
170+
_, err := apiserver.Clientset.AppsV1().Deployments(ns).Patch(d.Name, types.MergePatchType, patchBytes)
168171
if err != nil {
169172
log.Error(err.Error())
170173
}
@@ -174,82 +177,6 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab
174177
}
175178
}
176179

177-
func updateLabels(deployment *v1.Deployment, clusterName string, newLabels map[string]string, ns string) error {
178-
179-
var err error
180-
181-
log.Debugf("%v are the labels to apply", newLabels)
182-
183-
var patchBytes, newData, origData []byte
184-
origData, err = json.Marshal(deployment)
185-
if err != nil {
186-
return err
187-
}
188-
189-
accessor, err2 := meta.Accessor(deployment)
190-
if err2 != nil {
191-
return err2
192-
}
193-
194-
objLabels := accessor.GetLabels()
195-
if objLabels == nil {
196-
objLabels = make(map[string]string)
197-
}
198-
199-
//update the deployment labels
200-
for key, value := range newLabels {
201-
objLabels[key] = value
202-
}
203-
log.Debugf("updated labels are %v", objLabels)
204-
205-
accessor.SetLabels(objLabels)
206-
newData, err = json.Marshal(deployment)
207-
if err != nil {
208-
return err
209-
}
210-
211-
patchBytes, err = jsonpatch.CreateMergePatch(origData, newData)
212-
if err != nil {
213-
return err
214-
}
215-
216-
_, err = apiserver.Clientset.AppsV1().Deployments(ns).Patch(clusterName, types.MergePatchType, patchBytes, "")
217-
if err != nil {
218-
log.Debugf("error updating patching deployment %s", err.Error())
219-
}
220-
return err
221-
222-
}
223-
224-
func PatchPgcluster(newLabels map[string]string, oldCRD crv1.Pgcluster, ns string) error {
225-
226-
oldData, err := json.Marshal(oldCRD)
227-
if err != nil {
228-
return err
229-
}
230-
if oldCRD.ObjectMeta.Labels == nil {
231-
oldCRD.ObjectMeta.Labels = make(map[string]string)
232-
}
233-
for key, value := range newLabels {
234-
oldCRD.ObjectMeta.Labels[key] = value
235-
}
236-
var newData, patchBytes []byte
237-
newData, err = json.Marshal(oldCRD)
238-
if err != nil {
239-
return err
240-
}
241-
patchBytes, err = jsonpatch.CreateMergePatch(oldData, newData)
242-
if err != nil {
243-
return err
244-
}
245-
246-
log.Debug(string(patchBytes))
247-
_, err6 := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Patch(oldCRD.Spec.Name, types.MergePatchType, patchBytes)
248-
249-
return err6
250-
251-
}
252-
253180
func validateLabel(LabelCmdLabel, ns string) (map[string]string, error) {
254181
var err error
255182
labelMap := make(map[string]string)
@@ -361,11 +288,19 @@ func DeleteLabel(request *msgs.DeleteLabelRequest, ns string) msgs.LabelResponse
361288
}
362289

363290
func deleteLabels(items []crv1.Pgcluster, LabelCmdLabel string, labelsMap map[string]string, ns string) error {
364-
var err error
291+
patch := kubeapi.NewMergePatch()
292+
for key := range labelsMap {
293+
patch.Remove("metadata", "labels", key)
294+
}
295+
patchBytes, err := patch.Bytes()
296+
if err != nil {
297+
log.Error(err.Error())
298+
return err
299+
}
365300

366301
for i := 0; i < len(items); i++ {
367-
log.Debugf("deleting label from %s", items[i].Spec.Name)
368-
err = deletePatchPgcluster(labelsMap, items[i], ns)
302+
log.Debugf("patching cluster %s: %s", items[i].Spec.Name, patchBytes)
303+
_, err = apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Patch(items[i].Spec.Name, types.MergePatchType, patchBytes)
369304
if err != nil {
370305
log.Error(err.Error())
371306
return err
@@ -383,7 +318,8 @@ func deleteLabels(items []crv1.Pgcluster, LabelCmdLabel string, labelsMap map[st
383318
}
384319

385320
for _, d := range deployments.Items {
386-
err = deleteTheLabel(&d, items[i].Spec.Name, labelsMap, ns)
321+
log.Debugf("patching deployment %s: %s", d.Name, patchBytes)
322+
_, err = apiserver.Clientset.AppsV1().Deployments(ns).Patch(d.Name, types.MergePatchType, patchBytes)
387323
if err != nil {
388324
log.Error(err.Error())
389325
return err
@@ -393,79 +329,3 @@ func deleteLabels(items []crv1.Pgcluster, LabelCmdLabel string, labelsMap map[st
393329
}
394330
return err
395331
}
396-
397-
func deletePatchPgcluster(labelsMap map[string]string, oldCRD crv1.Pgcluster, ns string) error {
398-
399-
oldData, err := json.Marshal(oldCRD)
400-
if err != nil {
401-
return err
402-
}
403-
if oldCRD.ObjectMeta.Labels == nil {
404-
oldCRD.ObjectMeta.Labels = make(map[string]string)
405-
}
406-
for k := range labelsMap {
407-
delete(oldCRD.ObjectMeta.Labels, k)
408-
}
409-
410-
var newData, patchBytes []byte
411-
newData, err = json.Marshal(oldCRD)
412-
if err != nil {
413-
return err
414-
}
415-
patchBytes, err = jsonpatch.CreateMergePatch(oldData, newData)
416-
if err != nil {
417-
return err
418-
}
419-
420-
log.Debug(string(patchBytes))
421-
_, err6 := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Patch(oldCRD.Spec.Name, types.MergePatchType, patchBytes)
422-
423-
return err6
424-
425-
}
426-
427-
func deleteTheLabel(deployment *v1.Deployment, clusterName string, labelsMap map[string]string, ns string) error {
428-
429-
var err error
430-
431-
log.Debugf("%v are the labels to delete", labelsMap)
432-
433-
var patchBytes, newData, origData []byte
434-
origData, err = json.Marshal(deployment)
435-
if err != nil {
436-
return err
437-
}
438-
439-
accessor, err2 := meta.Accessor(deployment)
440-
if err2 != nil {
441-
return err2
442-
}
443-
444-
objLabels := accessor.GetLabels()
445-
if objLabels == nil {
446-
objLabels = make(map[string]string)
447-
}
448-
449-
for k := range labelsMap {
450-
delete(objLabels, k)
451-
}
452-
log.Debugf("revised labels after delete are %v", objLabels)
453-
454-
accessor.SetLabels(objLabels)
455-
newData, err = json.Marshal(deployment)
456-
if err != nil {
457-
return err
458-
}
459-
460-
patchBytes, err = jsonpatch.CreateMergePatch(origData, newData)
461-
if err != nil {
462-
return err
463-
}
464-
465-
_, err = apiserver.Clientset.AppsV1().Deployments(ns).Patch(deployment.Name, types.MergePatchType, patchBytes, "")
466-
if err != nil {
467-
log.Debugf("error patching deployment: %v", err.Error())
468-
}
469-
return err
470-
471-
}

internal/apiserver/policyservice/policyimpl.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import (
2121
"time"
2222

2323
"github.com/crunchydata/postgres-operator/internal/apiserver"
24-
"github.com/crunchydata/postgres-operator/internal/apiserver/labelservice"
2524
"github.com/crunchydata/postgres-operator/internal/config"
25+
"github.com/crunchydata/postgres-operator/internal/kubeapi"
2626
"github.com/crunchydata/postgres-operator/internal/util"
2727
crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1"
2828
msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs"
@@ -32,6 +32,7 @@ import (
3232
v1 "k8s.io/api/apps/v1"
3333
kerrors "k8s.io/apimachinery/pkg/api/errors"
3434
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35+
"k8s.io/apimachinery/pkg/types"
3536
)
3637

3738
// CreatePolicy ...
@@ -212,6 +213,13 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl
212213
labels := make(map[string]string)
213214
labels[request.Name] = "pgpolicy"
214215

216+
patch, err := kubeapi.NewMergePatch().Add("metadata", "labels")(labels).Bytes()
217+
if err != nil {
218+
resp.Status.Code = msgs.Error
219+
resp.Status.Msg = err.Error()
220+
return resp
221+
}
222+
215223
for _, d := range allDeployments {
216224
if d.ObjectMeta.Labels[config.LABEL_SERVICE_NAME] != d.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] {
217225
log.Debugf("skipping apply policy on deployment %s", d.Name)
@@ -238,13 +246,15 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl
238246
return resp
239247
}
240248

241-
err = util.UpdatePolicyLabels(apiserver.Clientset, d.ObjectMeta.Name, ns, labels)
249+
log.Debugf("patching deployment %s: %s", d.ObjectMeta.Name, patch)
250+
_, err = apiserver.Clientset.AppsV1().Deployments(ns).Patch(d.ObjectMeta.Name, types.MergePatchType, patch)
242251
if err != nil {
243252
log.Error(err)
244253
}
245254

246255
//update the pgcluster crd labels with the new policy
247-
err = labelservice.PatchPgcluster(map[string]string{request.Name: config.LABEL_PGPOLICY}, *cl, ns)
256+
log.Debugf("patching cluster %s: %s", cl.Name, patch)
257+
_, err = apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Patch(cl.Name, types.MergePatchType, patch)
248258
if err != nil {
249259
log.Error(err)
250260
}

internal/controller/job/backresthandler.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import (
2222
log "github.com/sirupsen/logrus"
2323
apiv1 "k8s.io/api/batch/v1"
2424
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/types"
2526

2627
"github.com/crunchydata/postgres-operator/internal/config"
2728
"github.com/crunchydata/postgres-operator/internal/controller"
29+
"github.com/crunchydata/postgres-operator/internal/kubeapi"
2830
"github.com/crunchydata/postgres-operator/internal/operator/backrest"
2931
backrestoperator "github.com/crunchydata/postgres-operator/internal/operator/backrest"
3032
clusteroperator "github.com/crunchydata/postgres-operator/internal/operator/cluster"
@@ -87,7 +89,12 @@ func (c *Controller) handleCloneBackrestRestoreUpdate(job *apiv1.Job) error {
8789

8890
// first, make sure the Pgtask resource knows that the job is complete,
8991
// which is using this legacy bit of code
90-
if err := util.Patch(c.Client.CrunchydataV1().RESTClient(), patchURL, crv1.JobCompletedStatus, patchResource, job.Name, namespace); err != nil {
92+
patch, err := kubeapi.NewJSONPatch().Add("spec", "status")(crv1.JobCompletedStatus).Bytes()
93+
if err == nil {
94+
log.Debugf("patching task %s: %s", job.Name, patch)
95+
_, err = c.Client.CrunchydataV1().Pgtasks(namespace).Patch(job.Name, types.JSONPatchType, patch)
96+
}
97+
if err != nil {
9198
log.Warn(err)
9299
// we can continue on, even if this fails...
93100
}
@@ -132,8 +139,12 @@ func (c *Controller) handleBackrestBackupUpdate(job *apiv1.Job) error {
132139
log.Debugf("got a backrest job status=%d", job.Status.Succeeded)
133140
log.Debugf("update the status to completed here for backrest %s job %s", labels[config.LABEL_PG_CLUSTER], job.Name)
134141

135-
if err := util.Patch(c.Client.CrunchydataV1().RESTClient(), patchURL, crv1.JobCompletedStatus, patchResource, job.Name,
136-
job.ObjectMeta.Namespace); err != nil {
142+
patch, err := kubeapi.NewJSONPatch().Add("spec", "status")(crv1.JobCompletedStatus).Bytes()
143+
if err == nil {
144+
log.Debugf("patching task %s: %s", job.Name, patch)
145+
_, err = c.Client.CrunchydataV1().Pgtasks(job.Namespace).Patch(job.Name, types.JSONPatchType, patch)
146+
}
147+
if err != nil {
137148
log.Errorf("error in patching pgtask %s: %s", job.ObjectMeta.SelfLink, err.Error())
138149
}
139150
publishBackupComplete(labels[config.LABEL_PG_CLUSTER], job.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], job.ObjectMeta.Labels[config.LABEL_PGOUSER], "pgbackrest", job.ObjectMeta.Namespace, "")

internal/controller/job/jobcontroller.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ type Controller struct {
3030
Informer batchinformers.JobInformer
3131
}
3232

33-
const (
34-
patchResource = "pgtasks"
35-
patchURL = "/spec/status"
36-
)
37-
3833
// onAdd is called when a postgresql operator job is created and an associated add event is
3934
// generated
4035
func (c *Controller) onAdd(obj interface{}) {

0 commit comments

Comments
 (0)