Skip to content

Commit a9281a7

Browse files
authored
Merge pull request #7 from pusher/fix-annotations
Fix annotation removal bug
2 parents a152bd1 + 4d64411 commit a9281a7

File tree

2 files changed

+95
-22
lines changed

2 files changed

+95
-22
lines changed

pkg/quack/quack.go

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"html/template"
88
"net/http"
9+
"strings"
910

1011
mergepatch "github.com/evanphx/json-patch"
1112
"github.com/golang/glog"
@@ -19,6 +20,7 @@ import (
1920

2021
const (
2122
lastAppliedConfigPath = "/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration"
23+
quackAnnotationPrefix = "/metadata/annotations/quack.pusher.com"
2224
leftDelimAnnotation = "quack.pusher.com/left-delim"
2325
rightDelimAnnotation = "quack.pusher.com/right-delim"
2426
)
@@ -167,7 +169,7 @@ func createPatch(old []byte, new []byte) ([]byte, error) {
167169
allowedOps := []jsonpatch.JsonPatchOperation{}
168170
for _, op := range patch {
169171
// Don't patch the lastAppliedConfig created by kubectl
170-
if op.Path == lastAppliedConfigPath {
172+
if op.Path == lastAppliedConfigPath || strings.HasPrefix(op.Path, quackAnnotationPrefix) {
171173
continue
172174
}
173175
allowedOps = append(allowedOps, op)
@@ -181,21 +183,28 @@ func createPatch(old []byte, new []byte) ([]byte, error) {
181183
}
182184

183185
func getTemplateInput(data []byte) ([]byte, error) {
184-
// Remove annotations from input template
185-
removeAnnotations := []byte(`[
186-
{"op": "remove", "path": "/metadata/annotations"}
187-
]`)
188-
patch, err := mergepatch.DecodePatch(removeAnnotations)
186+
// Fetch object meta into object
187+
objectMeta, err := getObjectMeta(data)
189188
if err != nil {
190-
return []byte{}, fmt.Errorf("unable to decode remove annotation patch: %v", err)
189+
return nil, fmt.Errorf("error reading object metadata: %v", err)
191190
}
192191

193-
// Apply patch to remove annotations
194-
templateInput, err := patch.Apply(data)
195-
if err != nil {
196-
return []byte{}, fmt.Errorf("unable to apply remove annotation patch: %v", err)
192+
var patchedData []byte
193+
for annotation := range objectMeta.Annotations {
194+
if strings.HasPrefix(annotation, "quack.pusher.com") {
195+
// Remove annotations from input template
196+
escapedAnnotation := strings.Replace(annotation, "/", "~1", -1)
197+
patch := []byte(fmt.Sprintf(`[
198+
{"op": "remove", "path": "/metadata/annotations/%s"}
199+
]`, escapedAnnotation))
200+
patchedData, err = applyPatch(data, patch)
201+
if err != nil {
202+
return nil, fmt.Errorf("error removing annotation %s: %v", annotation, err)
203+
}
204+
}
197205
}
198-
return templateInput, nil
206+
207+
return patchedData, nil
199208
}
200209

201210
func requestHasAnnotation(requiredAnnotation string, raw []byte) (bool, error) {
@@ -204,23 +213,45 @@ func requestHasAnnotation(requiredAnnotation string, raw []byte) (bool, error) {
204213
}
205214

206215
// Fetch object meta into object
216+
objectMeta, err := getObjectMeta(raw)
217+
if err != nil {
218+
return false, fmt.Errorf("error reading object metadata: %v", err)
219+
}
220+
221+
glog.V(6).Infof("Requested Object Annotations: %v", objectMeta.Annotations)
222+
223+
// Check required annotation exists in struct
224+
if _, ok := objectMeta.Annotations[requiredAnnotation]; ok {
225+
return true, nil
226+
}
227+
return false, nil
228+
}
229+
230+
func getObjectMeta(raw []byte) (metav1.ObjectMeta, error) {
207231
requestMeta := struct {
208232
metav1.ObjectMeta `json:"metadata"`
209233
}{
210234
ObjectMeta: metav1.ObjectMeta{},
211235
}
212236
err := json.Unmarshal(raw, &requestMeta)
213237
if err != nil {
214-
return false, fmt.Errorf("failed ot unmarshal input: %v", err)
238+
return metav1.ObjectMeta{}, fmt.Errorf("failed to unmarshal input: %v", err)
215239
}
240+
return requestMeta.ObjectMeta, nil
241+
}
216242

217-
glog.V(6).Infof("Requested Object Annotions: %v", requestMeta.ObjectMeta.Annotations)
243+
func applyPatch(data, patchBytes []byte) ([]byte, error) {
244+
patch, err := mergepatch.DecodePatch(patchBytes)
245+
if err != nil {
246+
return nil, fmt.Errorf("unable to decode patch: %v", err)
247+
}
218248

219-
// Check required annotation exists in struct
220-
if _, ok := requestMeta.ObjectMeta.Annotations[requiredAnnotation]; ok {
221-
return true, nil
249+
// Apply patch to remove annotations
250+
patchedData, err := patch.Apply(data)
251+
if err != nil {
252+
return nil, fmt.Errorf("unable to apply patch: %v", err)
222253
}
223-
return false, nil
254+
return patchedData, nil
224255
}
225256

226257
type delimiters struct {
@@ -240,7 +271,7 @@ func getDelims(raw []byte) (delimiters, error) {
240271
return delimiters{}, fmt.Errorf("failed ot unmarshal input: %v", err)
241272
}
242273

243-
glog.V(6).Infof("Requested Object Annotions: %v", requestMeta.ObjectMeta.Annotations)
274+
glog.V(6).Infof("Requested Object Annotations: %v", requestMeta.ObjectMeta.Annotations)
244275

245276
left, lOk := requestMeta.ObjectMeta.Annotations[leftDelimAnnotation]
246277
right, rOk := requestMeta.ObjectMeta.Annotations[rightDelimAnnotation]

pkg/quack/quack_test.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,42 @@ func TestRenderTemplate(t *testing.T) {
4848
assert.Equal(t, values["B"], output.Beta, "Value for B should be substituted for Beta output")
4949
}
5050

51+
func TestRenderTemplateDoesntRemoveQuackAnnotations(t *testing.T) {
52+
values := make(map[string]string)
53+
input := struct {
54+
ObjectMeta metav1.ObjectMeta `json:"metadata"`
55+
}{
56+
ObjectMeta: metav1.ObjectMeta{
57+
Annotations: map[string]string{
58+
"quack.pusher.com/foo": "bar",
59+
},
60+
},
61+
}
62+
63+
inputBytes, err := json.Marshal(input)
64+
if err != nil {
65+
assert.FailNowf(t, "jsonError", "Failed to marshal test input: %v", err)
66+
}
67+
68+
fmt.Printf("Template Test Input: %s\n", string(inputBytes))
69+
70+
outputBytes, err := renderTemplate(inputBytes, values, delimiters{})
71+
if err != nil {
72+
assert.FailNowf(t, "methodError", "Failed rendering template: %v", err)
73+
}
74+
75+
fmt.Printf("Template Test Output: %s\n", string(outputBytes))
76+
output := struct {
77+
ObjectMeta metav1.ObjectMeta `json:"metadata"`
78+
}{}
79+
err = json.Unmarshal(outputBytes, &output)
80+
if err != nil {
81+
assert.FailNowf(t, "jsonError", "Failed to unmarshal template output: %v", err)
82+
}
83+
84+
assert.Equal(t, input.ObjectMeta.Annotations, output.ObjectMeta.Annotations, "Annotations should not be changed")
85+
}
86+
5187
func TestRenderTemplateWithDelims(t *testing.T) {
5288
values := map[string]string{
5389
"A": "alpha",
@@ -152,13 +188,19 @@ func TestGetTemplateInput(t *testing.T) {
152188
object := testObject{
153189
ObjectMeta: metav1.ObjectMeta{
154190
Annotations: map[string]string{
155-
"annotation": "value",
191+
"annotation": "value",
192+
"quack.pusher.com/foo": "bar",
156193
},
157194
},
158195
Foo: "bar",
159196
}
160-
objectNoAnnotations := testObject{
197+
objectNoQuackAnnotations := testObject{
161198
Foo: "bar",
199+
ObjectMeta: metav1.ObjectMeta{
200+
Annotations: map[string]string{
201+
"annotation": "value",
202+
},
203+
},
162204
}
163205

164206
objectRaw, err := json.Marshal(object)
@@ -176,7 +218,7 @@ func TestGetTemplateInput(t *testing.T) {
176218
if err != nil {
177219
assert.FailNowf(t, "jsonError", "Error in unmarshall: %v", err)
178220
}
179-
assert.Equal(t, objectNoAnnotations, templateObject, "Object should have no annotations")
221+
assert.Equal(t, objectNoQuackAnnotations, templateObject, "Object should have no quack annotations")
180222
}
181223

182224
func TestGetDelims(t *testing.T) {

0 commit comments

Comments
 (0)