Skip to content

Commit f9356f8

Browse files
committed
Address comments
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
1 parent 72874ef commit f9356f8

File tree

7 files changed

+386
-108
lines changed

7 files changed

+386
-108
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ go 1.20
55
require (
66
dario.cat/mergo v1.0.0
77
github.com/bufbuild/buf v1.26.1
8-
github.com/evanphx/json-patch/v5 v5.6.0
98
github.com/go-logr/logr v1.2.4
109
github.com/google/go-cmp v0.5.9
1110
github.com/spf13/afero v1.9.5
@@ -40,6 +39,7 @@ require (
4039
github.com/docker/go-connections v0.4.0 // indirect
4140
github.com/docker/go-units v0.5.0 // indirect
4241
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
42+
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
4343
github.com/fatih/color v1.15.0 // indirect
4444
github.com/felixge/fgprof v0.9.3 // indirect
4545
github.com/fsnotify/fsnotify v1.6.0 // indirect

pkg/logging/logging.go

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ limitations under the License.
3737
package logging
3838

3939
import (
40+
"fmt"
41+
4042
"github.com/go-logr/logr"
4143
"sigs.k8s.io/controller-runtime/pkg/client"
4244
)
@@ -98,6 +100,9 @@ func (l logrLogger) WithValues(keysAndValues ...any) Logger {
98100
func ForResource(object client.Object) []string {
99101
ret := make([]string, 0, 10)
100102
gvk := object.GetObjectKind().GroupVersionKind()
103+
if gvk.Kind == "" {
104+
gvk.Kind = fmt.Sprintf("%T", object) // best effort for native Go types
105+
}
101106
ret = append(ret,
102107
"name", object.GetName(),
103108
"kind", gvk.Kind,

pkg/resource/api.go

+44-25
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,30 @@ import (
3232
// Error strings.
3333
const (
3434
errUpdateObject = "cannot update object"
35+
36+
// taken from k8s.io/apiserver. Not crucial to match, but for uniformity it
37+
// better should.
38+
// TODO(sttts): import from k8s.io/apiserver/pkg/registry/generic/registry when
39+
// kube has updated otel dependencies post-1.28.
40+
errOptimisticLock = "the object has been modified; please apply your changes to the latest version and try again"
3541
)
3642

3743
// An APIPatchingApplicator applies changes to an object by either creating or
3844
// patching it in a Kubernetes API server.
3945
type APIPatchingApplicator struct {
40-
client client.Client
41-
optionalLog logging.Logger // can be nil
46+
client client.Client
47+
log logging.Logger
4248
}
4349

4450
// NewAPIPatchingApplicator returns an Applicator that applies changes to an
4551
// object by either creating or patching it in a Kubernetes API server.
4652
func NewAPIPatchingApplicator(c client.Client) *APIPatchingApplicator {
47-
return &APIPatchingApplicator{client: c}
53+
return &APIPatchingApplicator{client: c, log: logging.NewNopLogger()}
4854
}
4955

5056
// WithLogger sets the logger on the APIPatchingApplicator.
5157
func (a *APIPatchingApplicator) WithLogger(l logging.Logger) *APIPatchingApplicator {
52-
a.optionalLog = l
58+
a.log = l
5359
return a
5460
}
5561

@@ -58,43 +64,49 @@ func (a *APIPatchingApplicator) WithLogger(l logging.Logger) *APIPatchingApplica
5864
// patched if the passed object has the same or an empty resource version.
5965
func (a *APIPatchingApplicator) Apply(ctx context.Context, obj client.Object, ao ...ApplyOption) error { //nolint:gocyclo // the logic here is crucial and deserves to stay in one method
6066
if obj.GetName() == "" && obj.GetGenerateName() != "" {
61-
return errors.Wrap(a.client.Create(ctx, obj), "cannot create object")
67+
log := a.log.WithValues(logging.ForResource(obj))
68+
log.Info("creating object")
69+
return a.client.Create(ctx, obj)
6270
}
6371

6472
current := obj.DeepCopyObject().(client.Object)
6573
err := a.client.Get(ctx, types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}, current)
6674
if kerrors.IsNotFound(err) {
6775
// TODO(negz): Apply ApplyOptions here too?
68-
return errors.Wrap(a.client.Create(ctx, obj), "cannot create object")
76+
return a.client.Create(ctx, obj)
6977
}
7078
if err != nil {
71-
return errors.Wrap(err, "cannot get object")
79+
return err
7280
}
7381

7482
// Note: this check would ideally not be necessary if the Apply signature
75-
// had a current object that we could us for the diff. But we have no
83+
// had a current object that we could use for the diff. But we have no
7684
// current and for consistency of the patch it matters that the object we
7785
// get above is the one that was originally used.
7886
if obj.GetResourceVersion() != "" && obj.GetResourceVersion() != current.GetResourceVersion() {
7987
gvr, err := groupResource(a.client, obj)
8088
if err != nil {
8189
return err
8290
}
83-
return kerrors.NewConflict(gvr, current.GetName(), errors.New("resource version does not match"))
91+
return kerrors.NewConflict(gvr, current.GetName(), errors.New(errOptimisticLock))
8492
}
8593

8694
for _, fn := range ao {
8795
if err := fn(ctx, current, obj); err != nil {
88-
return err
96+
return errors.Wrapf(err, "apply option failed for %s", HumanReadableReference(a.client, obj))
8997
}
9098
}
9199

92-
if err := LogDiff(a.optionalLog, current, obj); err != nil {
93-
return err
100+
// log diff
101+
patch := client.MergeFromWithOptions(current, client.MergeFromWithOptimisticLock{})
102+
patchBytes, err := patch.Data(obj)
103+
if err != nil {
104+
return errors.Wrapf(err, "failed to diff %s", HumanReadableReference(a.client, obj))
94105
}
106+
log := a.log.WithValues(logging.ForResource(obj))
107+
log.WithValues("diff", string(patchBytes)).Info("patching object")
95108

96-
// TODO(negz): Allow callers to override the kind of patch used.
97-
return errors.Wrap(a.client.Patch(ctx, obj, client.MergeFromWithOptions(current, client.MergeFromWithOptimisticLock{})), "cannot patch object")
109+
return a.client.Patch(ctx, obj, client.RawPatch(patch.Type(), patchBytes))
98110
}
99111

100112
func groupResource(c client.Client, o client.Object) (schema.GroupResource, error) {
@@ -112,8 +124,8 @@ func groupResource(c client.Client, o client.Object) (schema.GroupResource, erro
112124
// An APIUpdatingApplicator applies changes to an object by either creating or
113125
// updating it in a Kubernetes API server.
114126
type APIUpdatingApplicator struct {
115-
client client.Client
116-
optionalLog logging.Logger // can be nil
127+
client client.Client
128+
log logging.Logger
117129
}
118130

119131
// NewAPIUpdatingApplicator returns an Applicator that applies changes to an
@@ -122,43 +134,50 @@ type APIUpdatingApplicator struct {
122134
// Deprecated: Use NewAPIPatchingApplicator instead. The updating applicator
123135
// can lead to data-loss if the Golang types in this process are not up-to-date.
124136
func NewAPIUpdatingApplicator(c client.Client) *APIUpdatingApplicator {
125-
return &APIUpdatingApplicator{client: c}
137+
return &APIUpdatingApplicator{client: c, log: logging.NewNopLogger()}
126138
}
127139

128140
// WithLogger sets the logger on the APIUpdatingApplicator.
129141
func (a *APIUpdatingApplicator) WithLogger(l logging.Logger) *APIUpdatingApplicator {
130-
a.optionalLog = l
142+
a.log = l
131143
return a
132144
}
133145

134146
// Apply changes to the supplied object. The object will be created if it does
135147
// not exist, or updated if it does.
136148
func (a *APIUpdatingApplicator) Apply(ctx context.Context, obj client.Object, ao ...ApplyOption) error {
137149
if obj.GetName() == "" && obj.GetGenerateName() != "" {
138-
return errors.Wrap(a.client.Create(ctx, obj), "cannot create object")
150+
log := a.log.WithValues(logging.ForResource(obj))
151+
log.Info("creating object")
152+
return a.client.Create(ctx, obj)
139153
}
140154

141155
current := obj.DeepCopyObject().(client.Object)
142156
err := a.client.Get(ctx, types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}, current)
143157
if kerrors.IsNotFound(err) {
144158
// TODO(negz): Apply ApplyOptions here too?
145-
return errors.Wrap(a.client.Create(ctx, obj), "cannot create object")
159+
return a.client.Create(ctx, obj)
146160
}
147161
if err != nil {
148-
return errors.Wrap(err, "cannot get object")
162+
return err
149163
}
150164

151165
for _, fn := range ao {
152166
if err := fn(ctx, current, obj); err != nil {
153-
return err
167+
return errors.Wrapf(err, "apply option failed for %s", HumanReadableReference(a.client, obj))
154168
}
155169
}
156170

157-
if err := LogDiff(a.optionalLog, current, obj); err != nil {
158-
return err
171+
// log diff
172+
patch := client.MergeFromWithOptions(current, client.MergeFromWithOptimisticLock{})
173+
patchBytes, err := patch.Data(obj)
174+
if err != nil {
175+
return errors.Wrapf(err, "failed to diff %s", HumanReadableReference(a.client, obj))
159176
}
177+
log := a.log.WithValues(logging.ForResource(obj))
178+
log.WithValues("diff", string(patchBytes)).Info("updating object")
160179

161-
return errors.Wrap(a.client.Update(ctx, obj), "cannot update object")
180+
return a.client.Update(ctx, obj)
162181
}
163182

164183
// An APIFinalizer adds and removes finalizers to and from a resource.

0 commit comments

Comments
 (0)