Skip to content

Commit

Permalink
Add support for JSON patch in fake client
Browse files Browse the repository at this point in the history
Kubernetes-commit: a363b153851326ece7f81f4c1ae0a1ab8700a209
  • Loading branch information
Ville Aikas authored and k8s-publishing-bot committed Oct 2, 2018
1 parent cbd9965 commit 41406bf
Show file tree
Hide file tree
Showing 75 changed files with 348 additions and 175 deletions.
174 changes: 89 additions & 85 deletions Godeps/Godeps.json

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions dynamic/fake/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,25 +331,26 @@ func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface,
panic("math broke")
}

// TODO: opts are currently ignored.
func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootPatchAction(c.resource, name, data), &metav1.Status{Status: "dynamic patch fail"})
Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})

case len(c.namespace) == 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})

case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewPatchAction(c.resource, c.namespace, name, data), &metav1.Status{Status: "dynamic patch fail"})
Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})

case len(c.namespace) > 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})

}

Expand Down
131 changes: 131 additions & 0 deletions dynamic/fake/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,28 @@ limitations under the License.
package fake

import (
"fmt"
"testing"

"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/diff"
)

const (
testGroup = "testgroup"
testVersion = "testversion"
testResource = "testkinds"
testNamespace = "testns"
testName = "testname"
testKind = "TestKind"
testAPIVersion = "testgroup/testversion"
)

func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
Expand All @@ -40,6 +52,12 @@ func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Uns
}
}

func newUnstructuredWithSpec(spec map[string]interface{}) *unstructured.Unstructured {
u := newUnstructured(testAPIVersion, testKind, testNamespace, testName)
u.Object["spec"] = spec
return u
}

func TestList(t *testing.T) {
scheme := runtime.NewScheme()

Expand All @@ -64,3 +82,116 @@ func TestList(t *testing.T) {
t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))
}
}

type patchTestCase struct {
name string
object runtime.Object
patchType types.PatchType
patchBytes []byte
wantErrMsg string
expectedPatchedObject runtime.Object
}

func (tc *patchTestCase) runner(t *testing.T) {
client := NewSimpleDynamicClient(runtime.NewScheme(), tc.object)
resourceInterface := client.Resource(schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}).Namespace(testNamespace)

got, recErr := resourceInterface.Patch(testName, tc.patchType, tc.patchBytes, metav1.UpdateOptions{})

if err := tc.verifyErr(recErr); err != nil {
t.Error(err)
}

if err := tc.verifyResult(got); err != nil {
t.Error(err)
}

}

// verifyErr verifies that the given error returned from Patch is the error
// expected by the test case.
func (tc *patchTestCase) verifyErr(err error) error {
if tc.wantErrMsg != "" && err == nil {
return fmt.Errorf("want error, got nil")
}

if tc.wantErrMsg == "" && err != nil {
return fmt.Errorf("want no error, got %v", err)
}

if err != nil {
if want, got := tc.wantErrMsg, err.Error(); want != got {
return fmt.Errorf("incorrect error: want: %q got: %q", want, got)
}
}
return nil
}

func (tc *patchTestCase) verifyResult(result *unstructured.Unstructured) error {
if tc.expectedPatchedObject == nil && result == nil {
return nil
}
if !equality.Semantic.DeepEqual(result, tc.expectedPatchedObject) {
return fmt.Errorf("unexpected diff in received object: %s", diff.ObjectGoPrintDiff(tc.expectedPatchedObject, result))
}
return nil
}

func TestPatch(t *testing.T) {
testCases := []patchTestCase{
{
name: "jsonpatch fails with merge type",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.StrategicMergePatchType,
patchBytes: []byte(`[]`),
wantErrMsg: "invalid JSON document",
}, {
name: "jsonpatch works with empty patch",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.JSONPatchType,
// No-op
patchBytes: []byte(`[]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
}, {
name: "jsonpatch works with simple change patch",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.JSONPatchType,
// change spec.foo from bar to foobar
patchBytes: []byte(`[{"op": "replace", "path": "/spec/foo", "value": "foobar"}]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "foobar"}),
}, {
name: "jsonpatch works with simple addition",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.JSONPatchType,
// add spec.newvalue = dummy
patchBytes: []byte(`[{"op": "add", "path": "/spec/newvalue", "value": "dummy"}]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar", "newvalue": "dummy"}),
}, {
name: "jsonpatch works with simple deletion",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar", "toremove": "shouldnotbehere"}),
patchType: types.JSONPatchType,
// remove spec.newvalue = dummy
patchBytes: []byte(`[{"op": "remove", "path": "/spec/toremove"}]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
}, {
name: "strategic merge patch fails with JSONPatch",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.StrategicMergePatchType,
// add spec.newvalue = dummy
patchBytes: []byte(`[{"op": "add", "path": "/spec/newvalue", "value": "dummy"}]`),
wantErrMsg: "invalid JSON document",
}, {
name: "merge patch fails as unsupported",
object: newUnstructured(testAPIVersion, testKind, testNamespace, testName),
patchType: types.MergePatchType,
patchBytes: []byte(`{}`),
wantErrMsg: "PatchType is not supported",
},
// TODO: Add tests for strategic merge using v1.Pod for example to ensure the test cases
// demonstrate expected use cases.
}

for _, tc := range testCases {
t.Run(tc.name, tc.runner)
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1/fake/fake_controllerrevision.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1/fake/fake_daemonset.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1/fake/fake_deployment.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1/fake/fake_replicaset.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1/fake/fake_statefulset.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1beta1/fake/fake_deployment.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1beta1/fake/fake_statefulset.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1beta2/fake/fake_daemonset.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1beta2/fake/fake_deployment.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1beta2/fake/fake_replicaset.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kubernetes/typed/apps/v1beta2/fake/fake_statefulset.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 41406bf

Please sign in to comment.