Make RESTClient more generic to API version, simplify version handling

RESTClient is an abstraction on top of arbitrary HTTP endpoints that
follow the Kubernetes API conventions. Refactored RESTClientFor so that
assumptions that are Kube specific happen outside of that method (so
others can reuse the RESTClient). Added more validation to client.New
to ensure clients give good input. Exposed APIVersion on RESTClient
as a method so that wrapper code (code that adds typed / structured
methods over rest endpoints like client.Client) can more easily make
decisions about what APIVersion it is running under.
This commit is contained in:
Clayton Coleman 2015-01-06 12:36:08 -05:00
parent d314862e46
commit b03fbf90f8
13 changed files with 233 additions and 88 deletions

View File

@ -44,7 +44,7 @@ func (c *Client) ReplicationControllers(namespace string) ReplicationControllerI
}
func (c *Client) Nodes() NodeInterface {
return newNodes(c, c.preV1Beta3)
return newNodes(c)
}
func (c *Client) Events(namespace string) EventInterface {
@ -78,9 +78,6 @@ type APIStatus interface {
// Client is the implementation of a Kubernetes client.
type Client struct {
*RESTClient
// preV1Beta3 is true for v1beta1 and v1beta2
preV1Beta3 bool
}
// ServerVersion retrieves and parses the server's version.
@ -132,3 +129,8 @@ func IsTimeout(err error) bool {
}
return false
}
// preV1Beta3 returns true if the provided API version is an API introduced before v1beta3.
func preV1Beta3(version string) bool {
return version == "v1beta1" || version == "v1beta2"
}

View File

@ -165,7 +165,7 @@ func (c *testClient) ValidateCommon(t *testing.T, err error) {
// buildResourcePath is a convenience function for knowing if a namespace should be in a path param or not
func buildResourcePath(namespace, resource string) string {
if len(namespace) > 0 {
if NamespaceInPathFor(testapi.Version()) {
if !(testapi.Version() == "v1beta1" || testapi.Version() == "v1beta2") {
return path.Join("ns", namespace, resource)
}
}
@ -183,7 +183,7 @@ func buildQueryValues(namespace string, query url.Values) url.Values {
}
}
if len(namespace) > 0 {
if !NamespaceInPathFor(testapi.Version()) {
if testapi.Version() == "v1beta1" || testapi.Version() == "v1beta2" {
v.Set("namespace", namespace)
}
}
@ -765,7 +765,7 @@ func TestNewMinionPath(t *testing.T) {
Response: Response{StatusCode: 200},
}
cl := c.Setup()
cl.preV1Beta3 = false
cl.apiVersion = "v1beta3"
err := cl.Nodes().Delete("foo")
c.Validate(t, nil, err)
}

View File

@ -90,23 +90,28 @@ func (f HTTPClientFunc) Do(req *http.Request) (*http.Response, error) {
type FakeRESTClient struct {
Client HTTPClient
Codec runtime.Codec
Legacy bool
Req *http.Request
Resp *http.Response
Err error
}
func (c *FakeRESTClient) Get() *Request {
return NewRequest(c, "GET", &url.URL{Host: "localhost"}, c.Codec, true)
return NewRequest(c, "GET", &url.URL{Host: "localhost"}, c.Codec, c.Legacy, c.Legacy)
}
func (c *FakeRESTClient) Put() *Request {
return NewRequest(c, "PUT", &url.URL{Host: "localhost"}, c.Codec, true)
return NewRequest(c, "PUT", &url.URL{Host: "localhost"}, c.Codec, c.Legacy, c.Legacy)
}
func (c *FakeRESTClient) Post() *Request {
return NewRequest(c, "POST", &url.URL{Host: "localhost"}, c.Codec, true)
return NewRequest(c, "POST", &url.URL{Host: "localhost"}, c.Codec, c.Legacy, c.Legacy)
}
func (c *FakeRESTClient) Delete() *Request {
return NewRequest(c, "DELETE", &url.URL{Host: "localhost"}, c.Codec, true)
return NewRequest(c, "DELETE", &url.URL{Host: "localhost"}, c.Codec, c.Legacy, c.Legacy)
}
func (c *FakeRESTClient) Do(req *http.Request) (*http.Response, error) {
c.Req = req
if c.Client != HTTPClient(nil) {

View File

@ -24,6 +24,7 @@ import (
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
// Config holds the common attributes that can be passed to a Kubernetes client on
@ -34,9 +35,19 @@ type Config struct {
// Prefix is the sub path of the server. If not specified, the client will set
// a default value. Use "/" to indicate the server root should be used
Prefix string
// Version is the API version to talk to. If not specified, the client will use
// the preferred version.
// Version is the API version to talk to. Must be provided when initializing
// a RESTClient directly. When initializing a Client, will be set with the default
// code version.
Version string
// LegacyBehavior defines whether the RESTClient should follow conventions that
// existed prior to v1beta3 in Kubernetes - namely, namespace (if specified)
// not being part of the path, and resource names allowing mixed case. Set to
// true when using Kubernetes v1beta1 or v1beta2.
LegacyBehavior bool
// Codec specifies the encoding and decoding behavior for runtime.Objects passed
// to a RESTClient or Client. Required when initializing a RESTClient, optional
// when initializing a Client.
Codec runtime.Codec
// Server requires Basic authentication
Username string
@ -80,16 +91,14 @@ type KubeletConfig struct {
// is not valid.
func New(c *Config) (*Client, error) {
config := *c
if config.Prefix == "" {
config.Prefix = "/api"
if err := SetKubernetesDefaults(&config); err != nil {
return nil, err
}
client, err := RESTClientFor(&config)
if err != nil {
return nil, err
}
version := defaultVersionFor(&config)
isPreV1Beta3 := (version == "v1beta1" || version == "v1beta2")
return &Client{client, isPreV1Beta3}, nil
return &Client{client}, nil
}
// NewOrDie creates a Kubernetes client and panics if the provided API version is not recognized.
@ -101,15 +110,37 @@ func NewOrDie(c *Config) *Client {
return client
}
// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
// object.
func RESTClientFor(config *Config) (*RESTClient, error) {
version := defaultVersionFor(config)
// Set version
// SetKubernetesDefaults sets default values on the provided client config for accessing the
// Kubernetes API or returns an error if any of the defaults are impossible or invalid.
func SetKubernetesDefaults(config *Config) error {
if config.Prefix == "" {
config.Prefix = "/api"
}
if len(config.Version) == 0 {
config.Version = defaultVersionFor(config)
}
version := config.Version
versionInterfaces, err := latest.InterfacesFor(version)
if err != nil {
return nil, fmt.Errorf("API version '%s' is not recognized (valid values: %s)", version, strings.Join(latest.Versions, ", "))
return fmt.Errorf("API version '%s' is not recognized (valid values: %s)", version, strings.Join(latest.Versions, ", "))
}
if config.Codec == nil {
config.Codec = versionInterfaces.Codec
}
config.LegacyBehavior = (version == "v1beta1" || version == "v1beta2")
return nil
}
// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
// object. Note that a RESTClient may require fields that are optional when initializing a Client.
// A RESTClient created by this method is generic - it expects to operate on an API that follows
// the Kubernetes conventions, but may not be the Kubernetes API.
func RESTClientFor(config *Config) (*RESTClient, error) {
if len(config.Version) == 0 {
return nil, fmt.Errorf("version is required when initializing a RESTClient")
}
if config.Codec == nil {
return nil, fmt.Errorf("Codec is required when initializing a RESTClient")
}
baseURL, err := defaultServerUrlFor(config)
@ -117,7 +148,7 @@ func RESTClientFor(config *Config) (*RESTClient, error) {
return nil, err
}
client := NewRESTClient(baseURL, versionInterfaces.Codec, NamespaceInPathFor(version))
client := NewRESTClient(baseURL, config.Version, config.Codec, config.LegacyBehavior)
transport, err := TransportFor(config)
if err != nil {
@ -229,9 +260,9 @@ func IsConfigTransportTLS(config *Config) bool {
return baseURL.Scheme == "https"
}
// defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor
// defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It
// requires Host and Version to be set prior to being called.
func defaultServerUrlFor(config *Config) (*url.URL, error) {
version := defaultVersionFor(config)
// TODO: move the default to secure when the apiserver supports TLS by default
// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
defaultTLS := config.CertFile != "" || config.Insecure
@ -239,7 +270,7 @@ func defaultServerUrlFor(config *Config) (*url.URL, error) {
if host == "" {
host = "localhost"
}
return DefaultServerURL(host, config.Prefix, version, defaultTLS)
return DefaultServerURL(host, config.Prefix, config.Version, defaultTLS)
}
// defaultVersionFor is shared between defaultServerUrlFor and RESTClientFor
@ -252,9 +283,3 @@ func defaultVersionFor(config *Config) string {
}
return version
}
// namespaceInPathFor is used to control what api version should use namespace in url paths
func NamespaceInPathFor(version string) bool {
// we use query param for v1beta1/v1beta2, v1beta3+ will use path param
return (version != "v1beta1" && version != "v1beta2")
}

View File

@ -85,6 +85,10 @@ func TestIsConfigTransportTLS(t *testing.T) {
},
}
for _, testCase := range testCases {
if err := SetKubernetesDefaults(testCase.Config); err != nil {
t.Errorf("setting defaults failed for %#v: %v", testCase.Config, err)
continue
}
useTLS := IsConfigTransportTLS(testCase.Config)
if testCase.TransportTLS != useTLS {
t.Errorf("expected %v for %#v", testCase.TransportTLS, testCase.Config)

View File

@ -35,18 +35,17 @@ type NodeInterface interface {
// nodes implements NodesInterface
type nodes struct {
r *Client
preV1Beta3 bool
r *Client
}
// newNodes returns a nodes object. Uses "minions" as the
// URL resource name for v1beta1 and v1beta2.
func newNodes(c *Client, isPreV1Beta3 bool) *nodes {
return &nodes{c, isPreV1Beta3}
func newNodes(c *Client) *nodes {
return &nodes{c}
}
func (c *nodes) resourceName() string {
if c.preV1Beta3 {
if preV1Beta3(c.r.APIVersion()) {
return "minions"
}
return "nodes"

View File

@ -89,8 +89,12 @@ type Request struct {
// whether to poll.
poller PollFunc
// If true, put ns/<namespace> in path; if false, add "?namespace=<namespace>" as a query parameter
namespaceInPath bool
// If true, add "?namespace=<namespace>" as a query parameter, if false put ns/<namespace> in path
// Query parameter is considered legacy behavior
namespaceInQuery bool
// If true, lowercase resource prior to inserting into a path, if false, leave it as is. Preserving
// case is considered legacy behavior.
preserveResourceCase bool
// generic components accessible via method setters
path string
@ -110,15 +114,18 @@ type Request struct {
body io.Reader
}
// NewRequest creates a new request with the core attributes.
func NewRequest(client HTTPClient, verb string, baseURL *url.URL, codec runtime.Codec, namespaceInPath bool) *Request {
// NewRequest creates a new request helper object for accessing runtime.Objects on a server.
func NewRequest(client HTTPClient, verb string, baseURL *url.URL,
codec runtime.Codec, namespaceInQuery bool, preserveResourceCase bool) *Request {
return &Request{
client: client,
verb: verb,
baseURL: baseURL,
codec: codec,
namespaceInPath: namespaceInPath,
path: baseURL.Path,
client: client,
verb: verb,
baseURL: baseURL,
path: baseURL.Path,
codec: codec,
namespaceInQuery: namespaceInQuery,
preserveResourceCase: preserveResourceCase,
}
}
@ -323,11 +330,15 @@ func (r *Request) Poller(poller PollFunc) *Request {
func (r *Request) finalURL() string {
p := r.path
if r.namespaceInPath {
if !r.namespaceInQuery {
p = path.Join(p, "ns", r.namespace)
}
if len(r.resource) != 0 {
p = path.Join(p, r.resource)
resource := r.resource
if !r.preserveResourceCase {
resource = strings.ToLower(resource)
}
p = path.Join(p, resource)
}
// Join trims trailing slashes, so preserve r.path's trailing slash for backwards compat if nothing was changed
if len(r.resourceName) != 0 || len(r.subpath) != 0 {
@ -342,7 +353,7 @@ func (r *Request) finalURL() string {
query.Add(key, value)
}
if !r.namespaceInPath && len(r.namespace) > 0 {
if r.namespaceInQuery && len(r.namespace) > 0 {
query.Add("namespace", r.namespace)
}

View File

@ -75,21 +75,21 @@ func TestRequestWithErrorWontChange(t *testing.T) {
}
func TestRequestPreservesBaseTrailingSlash(t *testing.T) {
r := &Request{baseURL: &url.URL{}, path: "/path/"}
r := &Request{baseURL: &url.URL{}, path: "/path/", namespaceInQuery: true}
if s := r.finalURL(); s != "/path/" {
t.Errorf("trailing slash should be preserved: %s", s)
}
}
func TestRequestAbsPathPreservesTrailingSlash(t *testing.T) {
r := (&Request{baseURL: &url.URL{}}).AbsPath("/foo/")
r := (&Request{baseURL: &url.URL{}, namespaceInQuery: true}).AbsPath("/foo/")
if s := r.finalURL(); s != "/foo/" {
t.Errorf("trailing slash should be preserved: %s", s)
}
}
func TestRequestAbsPathJoins(t *testing.T) {
r := (&Request{baseURL: &url.URL{}}).AbsPath("foo/bar", "baz")
r := (&Request{baseURL: &url.URL{}, namespaceInQuery: true}).AbsPath("foo/bar", "baz")
if s := r.finalURL(); s != "foo/bar/baz" {
t.Errorf("trailing slash should be preserved: %s", s)
}
@ -100,6 +100,7 @@ func TestRequestSetsNamespace(t *testing.T) {
baseURL: &url.URL{
Path: "/",
},
namespaceInQuery: true,
}).Namespace("foo")
if r.namespace == "" {
t.Errorf("namespace should be set: %#v", r)
@ -112,7 +113,6 @@ func TestRequestSetsNamespace(t *testing.T) {
baseURL: &url.URL{
Path: "/",
},
namespaceInPath: true,
}).Namespace("foo")
if s := r.finalURL(); s != "ns/foo" {
t.Errorf("namespace should be in path: %s", s)
@ -121,9 +121,8 @@ func TestRequestSetsNamespace(t *testing.T) {
func TestRequestOrdersNamespaceInPath(t *testing.T) {
r := (&Request{
baseURL: &url.URL{},
path: "/test/",
namespaceInPath: true,
baseURL: &url.URL{},
path: "/test/",
}).Name("bar").Resource("baz").Namespace("foo")
if s := r.finalURL(); s != "/test/ns/foo/baz/bar" {
t.Errorf("namespace should be in order in path: %s", s)
@ -212,7 +211,7 @@ func TestTransformResponse(t *testing.T) {
{Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
}
for i, test := range testCases {
r := NewRequest(nil, "", uri, testapi.Codec(), true)
r := NewRequest(nil, "", uri, testapi.Codec(), true, true)
if test.Response.Body == nil {
test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
}

View File

@ -35,10 +35,13 @@ import (
// Most consumers should use client.New() to get a Kubernetes API client.
type RESTClient struct {
baseURL *url.URL
// A string identifying the version of the API this client is expected to use.
apiVersion string
// namespaceInPath controls if URLs should encode the namespace as path param instead of query param
// needed for backward compatibility
namespaceInPath bool
// LegacyBehavior controls if URLs should encode the namespace as a query param,
// and if resource case is preserved for supporting older API conventions of
// Kubernetes. Newer clients should leave this false.
LegacyBehavior bool
// Codec is the encoding and decoding scheme that applies to a particular set of
// REST resources.
@ -59,10 +62,9 @@ type RESTClient struct {
// NewRESTClient creates a new RESTClient. This client performs generic REST functions
// such as Get, Put, Post, and Delete on specified paths. Codec controls encoding and
// decoding of responses from the server. If the namespace should be specified as part
// of the path (after the resource), set namespaceInPath to true, otherwise it will be
// passed as "namespace" in the query string.
func NewRESTClient(baseURL *url.URL, c runtime.Codec, namespaceInPath bool) *RESTClient {
// decoding of responses from the server. If this client should use the older, legacy
// API conventions from Kubernetes API v1beta1 and v1beta2, set legacyBehavior true.
func NewRESTClient(baseURL *url.URL, apiVersion string, c runtime.Codec, legacyBehavior bool) *RESTClient {
base := *baseURL
if !strings.HasSuffix(base.Path, "/") {
base.Path += "/"
@ -71,10 +73,12 @@ func NewRESTClient(baseURL *url.URL, c runtime.Codec, namespaceInPath bool) *RES
base.Fragment = ""
return &RESTClient{
baseURL: &base,
Codec: c,
baseURL: &base,
apiVersion: apiVersion,
namespaceInPath: namespaceInPath,
Codec: c,
LegacyBehavior: legacyBehavior,
// Make asynchronous requests by default
Sync: false,
@ -106,7 +110,7 @@ func (c *RESTClient) Verb(verb string) *Request {
if poller == nil {
poller = c.DefaultPoll
}
return NewRequest(c.Client, verb, c.baseURL, c.Codec, c.namespaceInPath).Poller(poller).Sync(c.Sync).Timeout(c.Timeout)
return NewRequest(c.Client, verb, c.baseURL, c.Codec, c.LegacyBehavior, c.LegacyBehavior).Poller(poller).Sync(c.Sync).Timeout(c.Timeout)
}
// Post begins a POST request. Short for c.Verb("POST").
@ -134,6 +138,7 @@ func (c *RESTClient) Operation(name string) *Request {
return c.Get().Resource("operations").Name(name).Sync(false).NoPoll()
}
// DefaultPoll performs a polling action based on the PollPeriod set on the Client.
func (c *RESTClient) DefaultPoll(name string) (*Request, bool) {
if c.PollPeriod == 0 {
return nil, false
@ -143,3 +148,8 @@ func (c *RESTClient) DefaultPoll(name string) (*Request, bool) {
// Make a poll request
return c.Operation(name).Poller(c.DefaultPoll), true
}
// APIVersion returns the APIVersion this RESTClient is expected to use.
func (c *RESTClient) APIVersion() string {
return c.apiVersion
}

View File

@ -31,19 +31,19 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
func TestChecksCodec(t *testing.T) {
func TestSetsCodec(t *testing.T) {
testCases := map[string]struct {
Err bool
Prefix string
Codec runtime.Codec
}{
"v1beta1": {false, "/v1beta1/", v1beta1.Codec},
"": {false, "/v1beta1/", v1beta1.Codec},
"v1beta2": {false, "/v1beta2/", v1beta2.Codec},
"v1beta1": {false, "/api/v1beta1/", v1beta1.Codec},
"": {false, "/api/v1beta1/", v1beta1.Codec},
"v1beta2": {false, "/api/v1beta2/", v1beta2.Codec},
"v1beta3": {true, "", nil},
}
for version, expected := range testCases {
client, err := RESTClientFor(&Config{Host: "127.0.0.1", Version: version})
client, err := New(&Config{Host: "127.0.0.1", Version: version})
switch {
case err == nil && expected.Err:
t.Errorf("expected error but was nil")
@ -54,15 +54,70 @@ func TestChecksCodec(t *testing.T) {
case err != nil:
continue
}
if e, a := expected.Prefix, client.baseURL.Path; e != a {
if e, a := expected.Prefix, client.RESTClient.baseURL.Path; e != a {
t.Errorf("expected %#v, got %#v", e, a)
}
if e, a := expected.Codec, client.Codec; e != a {
if e, a := expected.Codec, client.RESTClient.Codec; e != a {
t.Errorf("expected %#v, got %#v", e, a)
}
}
}
func TestSetDefaults(t *testing.T) {
testCases := []struct {
Config Config
After Config
Err bool
}{
{
Config{},
Config{
Prefix: "/api",
Version: latest.Version,
Codec: latest.Codec,
LegacyBehavior: (latest.Version == "v1beta1" || latest.Version == "v1beta2"),
},
false,
},
{
Config{
Version: "not_an_api",
},
Config{},
true,
},
}
for _, testCase := range testCases {
val := &testCase.Config
err := SetKubernetesDefaults(val)
switch {
case err == nil && testCase.Err:
t.Errorf("expected error but was nil")
continue
case err != nil && !testCase.Err:
t.Errorf("unexpected error %v", err)
continue
case err != nil:
continue
}
if *val != testCase.After {
t.Errorf("unexpected result object: %#v", val)
}
}
}
func TestRESTClientRequires(t *testing.T) {
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", Version: "", Codec: testapi.Codec()}); err == nil {
t.Errorf("unexpected non-error")
}
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", Version: "v1beta1"}); err == nil {
t.Errorf("unexpected non-error")
}
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", Version: testapi.Version(), Codec: testapi.Codec()}); err != nil {
t.Errorf("unexpected error: %v", err)
}
}
func TestValidatesHostParameter(t *testing.T) {
testCases := []struct {
Host string
@ -81,7 +136,7 @@ func TestValidatesHostParameter(t *testing.T) {
{"host/server", "", "", true},
}
for i, testCase := range testCases {
c, err := RESTClientFor(&Config{Host: testCase.Host, Prefix: testCase.Prefix, Version: "v1beta1"})
c, err := RESTClientFor(&Config{Host: testCase.Host, Prefix: testCase.Prefix, Version: "v1beta1", Codec: testapi.Codec()})
switch {
case err == nil && testCase.Err:
t.Errorf("expected error but was nil")
@ -110,7 +165,14 @@ func TestDoRequestBearer(t *testing.T) {
testServer := httptest.NewServer(&fakeHandler)
defer testServer.Close()
request, _ := http.NewRequest("GET", testServer.URL, nil)
c, err := RESTClientFor(&Config{Host: testServer.URL, BearerToken: "test"})
c, err := RESTClientFor(&Config{
Host: testServer.URL,
Version: testapi.Version(),
Codec: testapi.Codec(),
LegacyBehavior: true,
BearerToken: "test",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -133,7 +195,14 @@ func TestDoRequestAccepted(t *testing.T) {
}
testServer := httptest.NewServer(&fakeHandler)
defer testServer.Close()
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "test", Version: testapi.Version()})
c, err := RESTClientFor(&Config{
Host: testServer.URL,
Version: testapi.Version(),
Codec: testapi.Codec(),
LegacyBehavior: true,
Username: "test",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -167,7 +236,15 @@ func TestDoRequestAcceptedSuccess(t *testing.T) {
}
testServer := httptest.NewServer(&fakeHandler)
defer testServer.Close()
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass", Version: testapi.Version()})
c, err := RESTClientFor(&Config{
Host: testServer.URL,
Version: testapi.Version(),
Codec: testapi.Codec(),
LegacyBehavior: true,
Username: "user",
Password: "pass",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -198,7 +275,12 @@ func TestDoRequestFailed(t *testing.T) {
}
testServer := httptest.NewServer(&fakeHandler)
defer testServer.Close()
c, err := RESTClientFor(&Config{Host: testServer.URL})
c, err := RESTClientFor(&Config{
Host: testServer.URL,
Version: testapi.Version(),
Codec: testapi.Codec(),
LegacyBehavior: true,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -226,7 +308,15 @@ func TestDoRequestCreated(t *testing.T) {
}
testServer := httptest.NewServer(&fakeHandler)
defer testServer.Close()
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass", Version: testapi.Version()})
c, err := RESTClientFor(&Config{
Host: testServer.URL,
Version: testapi.Version(),
Codec: testapi.Codec(),
LegacyBehavior: true,
Username: "user",
Password: "pass",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

View File

@ -48,7 +48,7 @@ func getFakeClient(t *testing.T, validURLs []string) (ClientPosterFunc, *httptes
return func(mapping *meta.RESTMapping) (RESTClientPoster, error) {
fakeCodec := runtime.CodecFor(api.Scheme, "v1beta1")
fakeUri, _ := url.Parse(server.URL + "/api/v1beta1")
return client.NewRESTClient(fakeUri, fakeCodec, false), nil
return client.NewRESTClient(fakeUri, "v1beta1", fakeCodec, true), nil
}, server
}

View File

@ -38,7 +38,7 @@ import (
)
func makeNamespaceURL(namespace, suffix string) string {
if client.NamespaceInPathFor(testapi.Version()) {
if !(testapi.Version() == "v1beta1" || testapi.Version() == "v1beta2") {
return makeURL("/ns/" + namespace + suffix)
}
return makeURL(suffix + "?namespace=" + namespace)

View File

@ -88,7 +88,7 @@ func TestPollMinions(t *testing.T) {
}
func makeNamespaceURL(namespace, suffix string, isClient bool) string {
if client.NamespaceInPathFor(testapi.Version()) {
if !(testapi.Version() == "v1beta1" || testapi.Version() == "v1beta2") {
return makeURL("/ns/" + namespace + suffix)
}
// if this is a url the client should call, encode the url