support openapi in apply

This commit is contained in:
ymqytw 2017-11-21 10:21:55 -08:00
parent f1ad84a2c3
commit 0b0004e0c0
3 changed files with 522 additions and 417 deletions

View File

@ -86,6 +86,7 @@ go_library(
"//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/editor:go_default_library", "//pkg/kubectl/cmd/util/editor:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/explain:go_default_library", "//pkg/kubectl/explain:go_default_library",
"//pkg/kubectl/metricsutil:go_default_library", "//pkg/kubectl/metricsutil:go_default_library",
"//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/plugins:go_default_library",
@ -147,6 +148,7 @@ go_library(
"//vendor/k8s.io/client-go/tools/portforward:go_default_library", "//vendor/k8s.io/client-go/tools/portforward:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/transport/spdy:go_default_library", "//vendor/k8s.io/client-go/transport/spdy:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library",
], ],
) )
@ -214,6 +216,7 @@ go_test(
"//pkg/kubectl:go_default_library", "//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library", "//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/plugins:go_default_library",
"//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/scheme:go_default_library",
@ -241,6 +244,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",

View File

@ -37,11 +37,13 @@ import (
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
oapi "k8s.io/kube-openapi/pkg/util/proto"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
@ -127,6 +129,7 @@ func NewCmdApply(baseName string, f cmdutil.Factory, out, errOut io.Writer) *cob
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().Bool("all", false, "Select all resources in the namespace of the specified resource types.") cmd.Flags().Bool("all", false, "Select all resources in the namespace of the specified resource types.")
cmd.Flags().StringArray("prune-whitelist", []string{}, "Overwrite the default whitelist with <group/version/kind> for --prune") cmd.Flags().StringArray("prune-whitelist", []string{}, "Overwrite the default whitelist with <group/version/kind> for --prune")
cmd.Flags().Bool("openapi-patch", true, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
cmdutil.AddPrinterFlags(cmd) cmdutil.AddPrinterFlags(cmd)
cmdutil.AddRecordFlag(cmd) cmdutil.AddRecordFlag(cmd)
@ -193,6 +196,14 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
return err return err
} }
var openapiSchema openapi.Resources
if cmdutil.GetFlagBool(cmd, "openapi-patch") {
openapiSchema, err = f.OpenAPISchema()
if err != nil {
openapiSchema = nil
}
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace() cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
@ -312,9 +323,10 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
cascade: options.Cascade, cascade: options.Cascade,
timeout: options.Timeout, timeout: options.Timeout,
gracePeriod: options.GracePeriod, gracePeriod: options.GracePeriod,
openapiSchema: openapiSchema,
} }
patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name) patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name, errOut)
if err != nil { if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err) return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
} }
@ -570,9 +582,11 @@ type patcher struct {
cascade bool cascade bool
timeout time.Duration timeout time.Duration
gracePeriod int gracePeriod int
openapiSchema openapi.Resources
} }
func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string) ([]byte, runtime.Object, error) { func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
// Serialize the current configuration of the object from the server. // Serialize the current configuration of the object from the server.
current, err := runtime.Encode(p.encoder, obj) current, err := runtime.Encode(p.encoder, obj)
if err != nil { if err != nil {
@ -585,33 +599,55 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", obj), source, err) return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", obj), source, err)
} }
// Create the versioned struct from the type defined in the restmapping
// (which is the API version we'll be submitting the patch to)
versionedObject, err := scheme.Scheme.New(p.mapping.GroupVersionKind)
var patchType types.PatchType var patchType types.PatchType
var patch []byte var patch []byte
var lookupPatchMeta strategicpatch.LookupPatchMeta
var schema oapi.Schema
createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:" createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
switch { // Try to use openapi first if the openapi spec is available and can successfully calculate the patch.
case runtime.IsNotRegisteredError(err): // Otherwise, fall back to baked-in types.
// fall back to generic JSON merge patch if p.openapiSchema != nil {
patchType = types.MergePatchType if schema = p.openapiSchema.LookupResource(p.mapping.GroupVersionKind); schema != nil {
preconditions := []mergepatch.PreconditionFunc{mergepatch.RequireKeyUnchanged("apiVersion"), lookupPatchMeta = strategicpatch.PatchMetaFromOpenAPI{Schema: schema}
mergepatch.RequireKeyUnchanged("kind"), mergepatch.RequireMetadataKeyUnchanged("name")} if openapiPatch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.overwrite); err != nil {
patch, err = jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current, preconditions...) fmt.Fprintf(errOut, "warning: error calculating patch from openapi spec: %v\n", err)
if err != nil { } else {
if mergepatch.IsPreconditionFailed(err) { patchType = types.StrategicMergePatchType
return nil, nil, fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed") patch = openapiPatch
} }
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
} }
case err != nil: }
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("getting instance of versioned object for %v:", p.mapping.GroupVersionKind), source, err)
case err == nil: if patch == nil {
// Compute a three way strategic merge patch to send to server. // Create the versioned struct from the type defined in the restmapping
patchType = types.StrategicMergePatchType // (which is the API version we'll be submitting the patch to)
patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, versionedObject, p.overwrite) versionedObject, err := scheme.Scheme.New(p.mapping.GroupVersionKind)
if err != nil { switch {
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err) case runtime.IsNotRegisteredError(err):
// fall back to generic JSON merge patch
patchType = types.MergePatchType
preconditions := []mergepatch.PreconditionFunc{mergepatch.RequireKeyUnchanged("apiVersion"),
mergepatch.RequireKeyUnchanged("kind"), mergepatch.RequireMetadataKeyUnchanged("name")}
patch, err = jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current, preconditions...)
if err != nil {
if mergepatch.IsPreconditionFailed(err) {
return nil, nil, fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed")
}
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
}
case err != nil:
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("getting instance of versioned object for %v:", p.mapping.GroupVersionKind), source, err)
case err == nil:
// Compute a three way strategic merge patch to send to server.
patchType = types.StrategicMergePatchType
lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject)
if err != nil {
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
}
patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.overwrite)
if err != nil {
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
}
} }
} }
@ -623,9 +659,9 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names
return patch, patchedObj, err return patch, patchedObj, err
} }
func (p *patcher) patch(current runtime.Object, modified []byte, source, namespace, name string) ([]byte, runtime.Object, error) { func (p *patcher) patch(current runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
var getErr error var getErr error
patchBytes, patchObject, err := p.patchSimple(current, modified, source, namespace, name) patchBytes, patchObject, err := p.patchSimple(current, modified, source, namespace, name, errOut)
for i := 1; i <= maxPatchRetry && errors.IsConflict(err); i++ { for i := 1; i <= maxPatchRetry && errors.IsConflict(err); i++ {
if i > triesBeforeBackOff { if i > triesBeforeBackOff {
p.backOff.Sleep(backOffPeriod) p.backOff.Sleep(backOffPeriod)
@ -634,7 +670,7 @@ func (p *patcher) patch(current runtime.Object, modified []byte, source, namespa
if getErr != nil { if getErr != nil {
return nil, nil, getErr return nil, nil, getErr
} }
patchBytes, patchObject, err = p.patchSimple(current, modified, source, namespace, name) patchBytes, patchObject, err = p.patchSimple(current, modified, source, namespace, name, errOut)
} }
if err != nil && p.force { if err != nil && p.force {
patchBytes, patchObject, err = p.deleteAndCreate(modified, namespace, name) patchBytes, patchObject, err = p.deleteAndCreate(modified, namespace, name)

View File

@ -19,10 +19,12 @@ package cmd
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath"
"strings" "strings"
"testing" "testing"
@ -33,15 +35,33 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
) )
var (
fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "api", "openapi-spec", "swagger.json")}
testingOpenAPISchemaFns = []func() (openapi.Resources, error){nil, AlwaysErrorOpenAPISchemaFn, openAPISchemaFn}
AlwaysErrorOpenAPISchemaFn = func() (openapi.Resources, error) {
return nil, errors.New("cannot get openapi spec")
}
openAPISchemaFn = func() (openapi.Resources, error) {
s, err := fakeSchema.OpenAPISchema()
if err != nil {
return nil, err
}
return openapi.NewOpenAPIData(s)
}
)
func TestApplyExtraArgsFail(t *testing.T) { func TestApplyExtraArgsFail(t *testing.T) {
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{})
@ -410,38 +430,44 @@ func TestApplyObject(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
f, tf, _, _ := cmdtesting.NewAPIFactory() for _, fn := range testingOpenAPISchemaFns {
tf.Printer = &testPrinter{} f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{ tf.Printer = &testPrinter{}
NegotiatedSerializer: unstructuredSerializer, tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { NegotiatedSerializer: unstructuredSerializer,
switch p, m := req.URL.Path, req.Method; { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
case p == pathRC && m == "GET": switch p, m := req.URL.Path, req.Method; {
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) case p == pathRC && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
case p == pathRC && m == "PATCH": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
validatePatchApplication(t, req) case p == pathRC && m == "PATCH":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) validatePatchApplication(t, req)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
default: return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) default:
return nil, nil t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
} return nil, nil
}), }
} }),
tf.Namespace = "test" }
buf := bytes.NewBuffer([]byte{}) tf.OpenAPISchemaFunc = fn
errBuf := bytes.NewBuffer([]byte{}) tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply("kubectl", f, buf, errBuf) cmd := NewCmdApply("kubectl", f, buf, errBuf)
cmd.Flags().Set("filename", filenameRC) cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("output", "name") cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{}) cmd.Run(cmd, []string{})
// uses the name from the file, not the response // uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n" expectRC := "replicationcontroller/" + nameRC + "\n"
if buf.String() != expectRC { if buf.String() != expectRC {
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC) t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
}
if errBuf.String() != "" {
t.Fatalf("unexpected error output: %s", errBuf.String())
}
} }
} }
@ -466,39 +492,45 @@ func TestApplyObjectOutput(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
f, tf, _, _ := cmdtesting.NewAPIFactory() for _, fn := range testingOpenAPISchemaFns {
tf.Printer = &printers.YAMLPrinter{} f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{ tf.Printer = &printers.YAMLPrinter{}
NegotiatedSerializer: unstructuredSerializer, tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { NegotiatedSerializer: unstructuredSerializer,
switch p, m := req.URL.Path, req.Method; { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
case p == pathRC && m == "GET": switch p, m := req.URL.Path, req.Method; {
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) case p == pathRC && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
case p == pathRC && m == "PATCH": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
validatePatchApplication(t, req) case p == pathRC && m == "PATCH":
bodyRC := ioutil.NopCloser(bytes.NewReader(postPatchData)) validatePatchApplication(t, req)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil bodyRC := ioutil.NopCloser(bytes.NewReader(postPatchData))
default: return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) default:
return nil, nil t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
} return nil, nil
}), }
} }),
tf.Namespace = "test" }
buf := bytes.NewBuffer([]byte{}) tf.OpenAPISchemaFunc = fn
errBuf := bytes.NewBuffer([]byte{}) tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply("kubectl", f, buf, errBuf) cmd := NewCmdApply("kubectl", f, buf, errBuf)
cmd.Flags().Set("filename", filenameRC) cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("output", "yaml") cmd.Flags().Set("output", "yaml")
cmd.Run(cmd, []string{}) cmd.Run(cmd, []string{})
if !strings.Contains(buf.String(), "name: test-rc") { if !strings.Contains(buf.String(), "name: test-rc") {
t.Fatalf("unexpected output: %s\nexpected to contain: %s", buf.String(), "name: test-rc") t.Fatalf("unexpected output: %s\nexpected to contain: %s", buf.String(), "name: test-rc")
} }
if !strings.Contains(buf.String(), "post-patch: value") { if !strings.Contains(buf.String(), "post-patch: value") {
t.Fatalf("unexpected output: %s\nexpected to contain: %s", buf.String(), "post-patch: value") t.Fatalf("unexpected output: %s\nexpected to contain: %s", buf.String(), "post-patch: value")
}
if errBuf.String() != "" {
t.Fatalf("unexpected error output: %s", errBuf.String())
}
} }
} }
@ -507,54 +539,60 @@ func TestApplyRetry(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
firstPatch := true for _, fn := range testingOpenAPISchemaFns {
retry := false firstPatch := true
getCount := 0 retry := false
f, tf, _, _ := cmdtesting.NewAPIFactory() getCount := 0
tf.Printer = &testPrinter{} f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{ tf.Printer = &testPrinter{}
NegotiatedSerializer: unstructuredSerializer, tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { NegotiatedSerializer: unstructuredSerializer,
switch p, m := req.URL.Path, req.Method; { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
case p == pathRC && m == "GET": switch p, m := req.URL.Path, req.Method; {
getCount++ case p == pathRC && m == "GET":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) getCount++
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
case p == pathRC && m == "PATCH": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
if firstPatch { case p == pathRC && m == "PATCH":
firstPatch = false if firstPatch {
statusErr := kubeerr.NewConflict(schema.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first.")) firstPatch = false
bodyBytes, _ := json.Marshal(statusErr) statusErr := kubeerr.NewConflict(schema.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first."))
bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes)) bodyBytes, _ := json.Marshal(statusErr)
return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes))
return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil
}
retry = true
validatePatchApplication(t, req)
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
} }
retry = true }),
validatePatchApplication(t, req) }
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) tf.OpenAPISchemaFunc = fn
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil tf.Namespace = "test"
default: buf := bytes.NewBuffer([]byte{})
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) errBuf := bytes.NewBuffer([]byte{})
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply("kubectl", f, buf, errBuf) cmd := NewCmdApply("kubectl", f, buf, errBuf)
cmd.Flags().Set("filename", filenameRC) cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("output", "name") cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{}) cmd.Run(cmd, []string{})
if !retry || getCount != 2 { if !retry || getCount != 2 {
t.Fatalf("apply didn't retry when get conflict error") t.Fatalf("apply didn't retry when get conflict error")
} }
// uses the name from the file, not the response // uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n" expectRC := "replicationcontroller/" + nameRC + "\n"
if buf.String() != expectRC { if buf.String() != expectRC {
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC) t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
}
if errBuf.String() != "" {
t.Fatalf("unexpected error output: %s", errBuf.String())
}
} }
} }
@ -682,55 +720,61 @@ func testApplyMultipleObjects(t *testing.T, asList bool) {
nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC) nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC)
pathSVC := "/namespaces/test/services/" + nameSVC pathSVC := "/namespaces/test/services/" + nameSVC
f, tf, _, _ := cmdtesting.NewAPIFactory() for _, fn := range testingOpenAPISchemaFns {
tf.Printer = &testPrinter{} f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{ tf.Printer = &testPrinter{}
NegotiatedSerializer: unstructuredSerializer, tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { NegotiatedSerializer: unstructuredSerializer,
switch p, m := req.URL.Path, req.Method; { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
case p == pathRC && m == "GET": switch p, m := req.URL.Path, req.Method; {
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) case p == pathRC && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
case p == pathRC && m == "PATCH": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
validatePatchApplication(t, req) case p == pathRC && m == "PATCH":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) validatePatchApplication(t, req)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
case p == pathSVC && m == "GET": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC)) case p == pathSVC && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
case p == pathSVC && m == "PATCH": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil
validatePatchApplication(t, req) case p == pathSVC && m == "PATCH":
bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC)) validatePatchApplication(t, req)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
default: return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) default:
return nil, nil t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
} return nil, nil
}), }
} }),
tf.Namespace = "test" }
buf := bytes.NewBuffer([]byte{}) tf.OpenAPISchemaFunc = fn
errBuf := bytes.NewBuffer([]byte{}) tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply("kubectl", f, buf, errBuf) cmd := NewCmdApply("kubectl", f, buf, errBuf)
if asList { if asList {
cmd.Flags().Set("filename", filenameRCSVC) cmd.Flags().Set("filename", filenameRCSVC)
} else { } else {
cmd.Flags().Set("filename", filenameRC) cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("filename", filenameSVC) cmd.Flags().Set("filename", filenameSVC)
} }
cmd.Flags().Set("output", "name") cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{}) cmd.Run(cmd, []string{})
// Names should come from the REST response, NOT the files // Names should come from the REST response, NOT the files
expectRC := "replicationcontroller/" + nameRC + "\n" expectRC := "replicationcontroller/" + nameRC + "\n"
expectSVC := "service/" + nameSVC + "\n" expectSVC := "service/" + nameSVC + "\n"
// Test both possible orders since output is non-deterministic. // Test both possible orders since output is non-deterministic.
expectOne := expectRC + expectSVC expectOne := expectRC + expectSVC
expectTwo := expectSVC + expectRC expectTwo := expectSVC + expectRC
if buf.String() != expectOne && buf.String() != expectTwo { if buf.String() != expectOne && buf.String() != expectTwo {
t.Fatalf("unexpected output: %s\nexpected: %s OR %s", buf.String(), expectOne, expectTwo) t.Fatalf("unexpected output: %s\nexpected: %s OR %s", buf.String(), expectOne, expectTwo)
}
if errBuf.String() != "" {
t.Fatalf("unexpected error output: %s", errBuf.String())
}
} }
} }
@ -760,62 +804,67 @@ func TestApplyNULLPreservation(t *testing.T) {
verifiedPatch := false verifiedPatch := false
deploymentBytes := readDeploymentFromFile(t, filenameDeployObjServerside) deploymentBytes := readDeploymentFromFile(t, filenameDeployObjServerside)
f, tf, _, _ := cmdtesting.NewTestFactory() for _, fn := range testingOpenAPISchemaFns {
tf.UnstructuredClient = &fake.RESTClient{ f, tf, _, _ := cmdtesting.NewAPIFactory()
NegotiatedSerializer: unstructuredSerializer, tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { NegotiatedSerializer: unstructuredSerializer,
switch p, m := req.URL.Path, req.Method; { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
case p == deploymentPath && m == "GET": switch p, m := req.URL.Path, req.Method; {
body := ioutil.NopCloser(bytes.NewReader(deploymentBytes)) case p == deploymentPath && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil body := ioutil.NopCloser(bytes.NewReader(deploymentBytes))
case p == deploymentPath && m == "PATCH": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
patch, err := ioutil.ReadAll(req.Body) case p == deploymentPath && m == "PATCH":
if err != nil { patch, err := ioutil.ReadAll(req.Body)
t.Fatal(err) if err != nil {
t.Fatal(err)
}
patchMap := map[string]interface{}{}
if err := json.Unmarshal(patch, &patchMap); err != nil {
t.Fatal(err)
}
annotationMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"})
if _, ok := annotationMap[api.LastAppliedConfigAnnotation]; !ok {
t.Fatalf("patch does not contain annotation:\n%s\n", patch)
}
strategy := walkMapPath(t, patchMap, []string{"spec", "strategy"})
if value, ok := strategy["rollingUpdate"]; !ok || value != nil {
t.Fatalf("patch did not retain null value in key: rollingUpdate:\n%s\n", patch)
}
verifiedPatch = true
// The real API server would had returned the patched object but Kubectl
// is ignoring the actual return object.
// TODO: Make this match actual server behavior by returning the patched object.
body := ioutil.NopCloser(bytes.NewReader(deploymentBytes))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
} }
}),
}
tf.OpenAPISchemaFunc = fn
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
patchMap := map[string]interface{}{} cmd := NewCmdApply("kubectl", f, buf, errBuf)
if err := json.Unmarshal(patch, &patchMap); err != nil { cmd.Flags().Set("filename", filenameDeployObjClientside)
t.Fatal(err) cmd.Flags().Set("output", "name")
}
annotationMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"})
if _, ok := annotationMap[api.LastAppliedConfigAnnotation]; !ok {
t.Fatalf("patch does not contain annotation:\n%s\n", patch)
}
strategy := walkMapPath(t, patchMap, []string{"spec", "strategy"})
if value, ok := strategy["rollingUpdate"]; !ok || value != nil {
t.Fatalf("patch did not retain null value in key: rollingUpdate:\n%s\n", patch)
}
verifiedPatch = true
// The real API server would had returned the patched object but Kubectl cmd.Run(cmd, []string{})
// is ignoring the actual return object.
// TODO: Make this match actual server behavior by returning the patched object.
body := ioutil.NopCloser(bytes.NewReader(deploymentBytes))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply("kubectl", f, buf, errBuf) expected := "deployment/" + deploymentName + "\n"
cmd.Flags().Set("filename", filenameDeployObjClientside) if buf.String() != expected {
cmd.Flags().Set("output", "name") t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected)
}
cmd.Run(cmd, []string{}) if errBuf.String() != "" {
t.Fatalf("unexpected error output: %s", errBuf.String())
expected := "deployment/" + deploymentName + "\n" }
if buf.String() != expected { if !verifiedPatch {
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected) t.Fatal("No server-side patch call detected")
} }
if !verifiedPatch {
t.Fatal("No server-side patch call detected")
} }
} }
@ -827,53 +876,58 @@ func TestUnstructuredApply(t *testing.T) {
verifiedPatch := false verifiedPatch := false
f, tf, _, _ := cmdtesting.NewAPIFactory() for _, fn := range testingOpenAPISchemaFns {
tf.Printer = &testPrinter{} f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{ tf.Printer = &testPrinter{}
NegotiatedSerializer: unstructuredSerializer, tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { NegotiatedSerializer: unstructuredSerializer,
switch p, m := req.URL.Path, req.Method; { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
case p == path && m == "GET": switch p, m := req.URL.Path, req.Method; {
body := ioutil.NopCloser(bytes.NewReader(curr)) case p == path && m == "GET":
return &http.Response{ body := ioutil.NopCloser(bytes.NewReader(curr))
StatusCode: 200, return &http.Response{
Header: defaultHeader(), StatusCode: 200,
Body: body}, nil Header: defaultHeader(),
case p == path && m == "PATCH": Body: body}, nil
contentType := req.Header.Get("Content-Type") case p == path && m == "PATCH":
if contentType != "application/merge-patch+json" { contentType := req.Header.Get("Content-Type")
t.Fatalf("Unexpected Content-Type: %s", contentType) if contentType != "application/merge-patch+json" {
t.Fatalf("Unexpected Content-Type: %s", contentType)
}
validatePatchApplication(t, req)
verifiedPatch = true
body := ioutil.NopCloser(bytes.NewReader(curr))
return &http.Response{
StatusCode: 200,
Header: defaultHeader(),
Body: body}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
} }
validatePatchApplication(t, req) }),
verifiedPatch = true }
tf.OpenAPISchemaFunc = fn
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
body := ioutil.NopCloser(bytes.NewReader(curr)) cmd := NewCmdApply("kubectl", f, buf, errBuf)
return &http.Response{ cmd.Flags().Set("filename", filenameWidgetClientside)
StatusCode: 200, cmd.Flags().Set("output", "name")
Header: defaultHeader(), cmd.Run(cmd, []string{})
Body: body}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test" expected := "widget/" + name + "\n"
buf := bytes.NewBuffer([]byte{}) if buf.String() != expected {
errBuf := bytes.NewBuffer([]byte{}) t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected)
}
cmd := NewCmdApply("kubectl", f, buf, errBuf) if errBuf.String() != "" {
cmd.Flags().Set("filename", filenameWidgetClientside) t.Fatalf("unexpected error output: %s", errBuf.String())
cmd.Flags().Set("output", "name") }
cmd.Run(cmd, []string{}) if !verifiedPatch {
t.Fatal("No server-side patch call detected")
expected := "widget/" + name + "\n" }
if buf.String() != expected {
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected)
}
if !verifiedPatch {
t.Fatal("No server-side patch call detected")
} }
} }
@ -890,76 +944,81 @@ func TestUnstructuredIdempotentApply(t *testing.T) {
verifiedPatch := false verifiedPatch := false
f, tf, _, _ := cmdtesting.NewAPIFactory() for _, fn := range testingOpenAPISchemaFns {
tf.Printer = &testPrinter{} f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{ tf.Printer = &testPrinter{}
NegotiatedSerializer: unstructuredSerializer, tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { NegotiatedSerializer: unstructuredSerializer,
switch p, m := req.URL.Path, req.Method; { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
case p == path && m == "GET": switch p, m := req.URL.Path, req.Method; {
body := ioutil.NopCloser(bytes.NewReader(serversideData)) case p == path && m == "GET":
return &http.Response{ body := ioutil.NopCloser(bytes.NewReader(serversideData))
StatusCode: 200, return &http.Response{
Header: defaultHeader(), StatusCode: 200,
Body: body}, nil Header: defaultHeader(),
case p == path && m == "PATCH": Body: body}, nil
// In idempotent updates, kubectl sends a logically empty case p == path && m == "PATCH":
// request body with the PATCH request. // In idempotent updates, kubectl sends a logically empty
// Should look like this: // request body with the PATCH request.
// Request Body: {"metadata":{"annotations":{}}} // Should look like this:
// Request Body: {"metadata":{"annotations":{}}}
patch, err := ioutil.ReadAll(req.Body) patch, err := ioutil.ReadAll(req.Body)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
}
contentType := req.Header.Get("Content-Type")
if contentType != "application/merge-patch+json" {
t.Fatalf("Unexpected Content-Type: %s", contentType)
}
patchMap := map[string]interface{}{}
if err := json.Unmarshal(patch, &patchMap); err != nil {
t.Fatal(err)
}
if len(patchMap) != 1 {
t.Fatalf("Unexpected Patch. Has more than 1 entry. path: %s", patch)
}
annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"})
if len(annotationsMap) != 0 {
t.Fatalf("Unexpected Patch. Found unexpected annotation: %s", patch)
}
verifiedPatch = true
body := ioutil.NopCloser(bytes.NewReader(serversideData))
return &http.Response{
StatusCode: 200,
Header: defaultHeader(),
Body: body}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
} }
}),
}
tf.OpenAPISchemaFunc = fn
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
contentType := req.Header.Get("Content-Type") cmd := NewCmdApply("kubectl", f, buf, errBuf)
if contentType != "application/merge-patch+json" { cmd.Flags().Set("filename", filenameWidgetClientside)
t.Fatalf("Unexpected Content-Type: %s", contentType) cmd.Flags().Set("output", "name")
} cmd.Run(cmd, []string{})
patchMap := map[string]interface{}{} expected := "widget/widget\n"
if err := json.Unmarshal(patch, &patchMap); err != nil { if buf.String() != expected {
t.Fatal(err) t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected)
} }
if len(patchMap) != 1 { if errBuf.String() != "" {
t.Fatalf("Unexpected Patch. Has more than 1 entry. path: %s", patch) t.Fatalf("unexpected error output: %s", errBuf.String())
} }
if !verifiedPatch {
annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"}) t.Fatal("No server-side patch call detected")
if len(annotationsMap) != 0 { }
t.Fatalf("Unexpected Patch. Found unexpected annotation: %s", patch)
}
verifiedPatch = true
body := ioutil.NopCloser(bytes.NewReader(serversideData))
return &http.Response{
StatusCode: 200,
Header: defaultHeader(),
Body: body}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply("kubectl", f, buf, errBuf)
cmd.Flags().Set("filename", filenameWidgetClientside)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
expected := "widget/widget\n"
if buf.String() != expected {
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected)
}
if !verifiedPatch {
t.Fatal("No server-side patch call detected")
} }
} }
@ -1093,8 +1152,6 @@ func TestForceApply(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
pathRCList := "/namespaces/test/replicationcontrollers" pathRCList := "/namespaces/test/replicationcontrollers"
deleted := false
counts := map[string]int{}
expected := map[string]int{ expected := map[string]int{
"getOk": 9, "getOk": 9,
"getNotFound": 1, "getNotFound": 1,
@ -1105,84 +1162,92 @@ func TestForceApply(t *testing.T) {
"post": 1, "post": 1,
} }
f, tf, _, _ := cmdtesting.NewAPIFactory() for _, fn := range testingOpenAPISchemaFns {
tf.Printer = &testPrinter{} deleted := false
tf.UnstructuredClient = &fake.RESTClient{ counts := map[string]int{}
NegotiatedSerializer: unstructuredSerializer, f, tf, _, _ := cmdtesting.NewAPIFactory()
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { tf.Printer = &testPrinter{}
switch p, m := req.URL.Path, req.Method; { tf.UnstructuredClient = &fake.RESTClient{
case strings.HasSuffix(p, pathRC) && m == "GET": NegotiatedSerializer: unstructuredSerializer,
if deleted { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
counts["getNotFound"]++ switch p, m := req.URL.Path, req.Method; {
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil case strings.HasSuffix(p, pathRC) && m == "GET":
if deleted {
counts["getNotFound"]++
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil
}
counts["getOk"]++
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case strings.HasSuffix(p, pathRCList) && m == "GET":
counts["getList"]++
rcObj := readUnstructuredFromFile(t, filenameRC)
list := &unstructured.UnstructuredList{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ReplicationControllerList",
},
Items: []unstructured.Unstructured{*rcObj},
}
listBytes, err := runtime.Encode(testapi.Default.Codec(), list)
if err != nil {
t.Fatal(err)
}
bodyRCList := ioutil.NopCloser(bytes.NewReader(listBytes))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRCList}, nil
case strings.HasSuffix(p, pathRC) && m == "PATCH":
counts["patch"]++
if counts["patch"] <= 6 {
statusErr := kubeerr.NewConflict(schema.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first."))
bodyBytes, _ := json.Marshal(statusErr)
bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes))
return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil
}
t.Fatalf("unexpected request: %#v after %v tries\n%#v", req.URL, counts["patch"], req)
return nil, nil
case strings.HasSuffix(p, pathRC) && m == "DELETE":
counts["delete"]++
deleted = true
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil
case strings.HasSuffix(p, pathRC) && m == "PUT":
counts["put"]++
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case strings.HasSuffix(p, pathRCList) && m == "POST":
counts["post"]++
deleted = false
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
} }
counts["getOk"]++ }),
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) }
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil tf.OpenAPISchemaFunc = fn
case strings.HasSuffix(p, pathRCList) && m == "GET": tf.Client = tf.UnstructuredClient
counts["getList"]++ tf.ClientConfig = &restclient.Config{}
rcObj := readUnstructuredFromFile(t, filenameRC) tf.Namespace = "test"
list := &unstructured.UnstructuredList{ buf := bytes.NewBuffer([]byte{})
Object: map[string]interface{}{ errBuf := bytes.NewBuffer([]byte{})
"apiVersion": "v1",
"kind": "ReplicationControllerList", cmd := NewCmdApply("kubectl", f, buf, errBuf)
}, cmd.Flags().Set("filename", filenameRC)
Items: []unstructured.Unstructured{*rcObj}, cmd.Flags().Set("output", "name")
} cmd.Flags().Set("force", "true")
listBytes, err := runtime.Encode(testapi.Default.Codec(), list) cmd.Run(cmd, []string{})
if err != nil {
t.Fatal(err) for method, exp := range expected {
} if exp != counts[method] {
bodyRCList := ioutil.NopCloser(bytes.NewReader(listBytes)) t.Errorf("Unexpected amount of %q API calls, wanted %v got %v", method, exp, counts[method])
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRCList}, nil
case strings.HasSuffix(p, pathRC) && m == "PATCH":
counts["patch"]++
if counts["patch"] <= 6 {
statusErr := kubeerr.NewConflict(schema.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first."))
bodyBytes, _ := json.Marshal(statusErr)
bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes))
return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil
}
t.Fatalf("unexpected request: %#v after %v tries\n%#v", req.URL, counts["patch"], req)
return nil, nil
case strings.HasSuffix(p, pathRC) && m == "DELETE":
counts["delete"]++
deleted = true
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil
case strings.HasSuffix(p, pathRC) && m == "PUT":
counts["put"]++
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case strings.HasSuffix(p, pathRCList) && m == "POST":
counts["post"]++
deleted = false
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
} }
}), }
}
tf.Client = tf.UnstructuredClient
tf.ClientConfig = defaultClientConfig()
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply("kubectl", f, buf, errBuf) if expected := "replicationcontroller/" + nameRC + "\n"; buf.String() != expected {
cmd.Flags().Set("filename", filenameRC) t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected)
cmd.Flags().Set("output", "name") }
cmd.Flags().Set("force", "true") if errBuf.String() != "" {
cmd.Run(cmd, []string{}) t.Fatalf("unexpected error output: %s", errBuf.String())
for method, exp := range expected {
if exp != counts[method] {
t.Errorf("Unexpected amount of %q API calls, wanted %v got %v", method, exp, counts[method])
} }
} }
if expected := "replicationcontroller/" + nameRC + "\n"; buf.String() != expected {
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected)
}
} }