mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-26 20:13:29 +00:00
Merge pull request #3269 from smarterclayton/fix_legacy_support
Make RESTClient more generic to API version, simplify version handling
This commit is contained in:
commit
3e0b4cfabc
@ -44,7 +44,7 @@ func (c *Client) ReplicationControllers(namespace string) ReplicationControllerI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Nodes() NodeInterface {
|
func (c *Client) Nodes() NodeInterface {
|
||||||
return newNodes(c, c.preV1Beta3)
|
return newNodes(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Events(namespace string) EventInterface {
|
func (c *Client) Events(namespace string) EventInterface {
|
||||||
@ -78,9 +78,6 @@ type APIStatus interface {
|
|||||||
// Client is the implementation of a Kubernetes client.
|
// Client is the implementation of a Kubernetes client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
*RESTClient
|
*RESTClient
|
||||||
|
|
||||||
// preV1Beta3 is true for v1beta1 and v1beta2
|
|
||||||
preV1Beta3 bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerVersion retrieves and parses the server's version.
|
// ServerVersion retrieves and parses the server's version.
|
||||||
@ -132,3 +129,8 @@ func IsTimeout(err error) bool {
|
|||||||
}
|
}
|
||||||
return false
|
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"
|
||||||
|
}
|
||||||
|
@ -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
|
// buildResourcePath is a convenience function for knowing if a namespace should be in a path param or not
|
||||||
func buildResourcePath(namespace, resource string) string {
|
func buildResourcePath(namespace, resource string) string {
|
||||||
if len(namespace) > 0 {
|
if len(namespace) > 0 {
|
||||||
if NamespaceInPathFor(testapi.Version()) {
|
if !(testapi.Version() == "v1beta1" || testapi.Version() == "v1beta2") {
|
||||||
return path.Join("ns", namespace, resource)
|
return path.Join("ns", namespace, resource)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,7 +183,7 @@ func buildQueryValues(namespace string, query url.Values) url.Values {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(namespace) > 0 {
|
if len(namespace) > 0 {
|
||||||
if !NamespaceInPathFor(testapi.Version()) {
|
if testapi.Version() == "v1beta1" || testapi.Version() == "v1beta2" {
|
||||||
v.Set("namespace", namespace)
|
v.Set("namespace", namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -765,7 +765,7 @@ func TestNewMinionPath(t *testing.T) {
|
|||||||
Response: Response{StatusCode: 200},
|
Response: Response{StatusCode: 200},
|
||||||
}
|
}
|
||||||
cl := c.Setup()
|
cl := c.Setup()
|
||||||
cl.preV1Beta3 = false
|
cl.apiVersion = "v1beta3"
|
||||||
err := cl.Nodes().Delete("foo")
|
err := cl.Nodes().Delete("foo")
|
||||||
c.Validate(t, nil, err)
|
c.Validate(t, nil, err)
|
||||||
}
|
}
|
||||||
|
@ -90,23 +90,28 @@ func (f HTTPClientFunc) Do(req *http.Request) (*http.Response, error) {
|
|||||||
type FakeRESTClient struct {
|
type FakeRESTClient struct {
|
||||||
Client HTTPClient
|
Client HTTPClient
|
||||||
Codec runtime.Codec
|
Codec runtime.Codec
|
||||||
|
Legacy bool
|
||||||
Req *http.Request
|
Req *http.Request
|
||||||
Resp *http.Response
|
Resp *http.Response
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *FakeRESTClient) Get() *Request {
|
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 {
|
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 {
|
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 {
|
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) {
|
func (c *FakeRESTClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
c.Req = req
|
c.Req = req
|
||||||
if c.Client != HTTPClient(nil) {
|
if c.Client != HTTPClient(nil) {
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"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
|
// 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
|
// 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
|
// a default value. Use "/" to indicate the server root should be used
|
||||||
Prefix string
|
Prefix string
|
||||||
// Version is the API version to talk to. If not specified, the client will use
|
// Version is the API version to talk to. Must be provided when initializing
|
||||||
// the preferred version.
|
// a RESTClient directly. When initializing a Client, will be set with the default
|
||||||
|
// code version.
|
||||||
Version string
|
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
|
// Server requires Basic authentication
|
||||||
Username string
|
Username string
|
||||||
@ -80,16 +91,14 @@ type KubeletConfig struct {
|
|||||||
// is not valid.
|
// is not valid.
|
||||||
func New(c *Config) (*Client, error) {
|
func New(c *Config) (*Client, error) {
|
||||||
config := *c
|
config := *c
|
||||||
if config.Prefix == "" {
|
if err := SetKubernetesDefaults(&config); err != nil {
|
||||||
config.Prefix = "/api"
|
return nil, err
|
||||||
}
|
}
|
||||||
client, err := RESTClientFor(&config)
|
client, err := RESTClientFor(&config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
version := defaultVersionFor(&config)
|
return &Client{client}, nil
|
||||||
isPreV1Beta3 := (version == "v1beta1" || version == "v1beta2")
|
|
||||||
return &Client{client, isPreV1Beta3}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOrDie creates a Kubernetes client and panics if the provided API version is not recognized.
|
// 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
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
|
// SetKubernetesDefaults sets default values on the provided client config for accessing the
|
||||||
// object.
|
// Kubernetes API or returns an error if any of the defaults are impossible or invalid.
|
||||||
func RESTClientFor(config *Config) (*RESTClient, error) {
|
func SetKubernetesDefaults(config *Config) error {
|
||||||
version := defaultVersionFor(config)
|
if config.Prefix == "" {
|
||||||
|
config.Prefix = "/api"
|
||||||
// Set version
|
}
|
||||||
|
if len(config.Version) == 0 {
|
||||||
|
config.Version = defaultVersionFor(config)
|
||||||
|
}
|
||||||
|
version := config.Version
|
||||||
versionInterfaces, err := latest.InterfacesFor(version)
|
versionInterfaces, err := latest.InterfacesFor(version)
|
||||||
if err != nil {
|
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)
|
baseURL, err := defaultServerUrlFor(config)
|
||||||
@ -117,7 +148,7 @@ func RESTClientFor(config *Config) (*RESTClient, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := NewRESTClient(baseURL, versionInterfaces.Codec, NamespaceInPathFor(version))
|
client := NewRESTClient(baseURL, config.Version, config.Codec, config.LegacyBehavior)
|
||||||
|
|
||||||
transport, err := TransportFor(config)
|
transport, err := TransportFor(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -229,9 +260,9 @@ func IsConfigTransportTLS(config *Config) bool {
|
|||||||
return baseURL.Scheme == "https"
|
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) {
|
func defaultServerUrlFor(config *Config) (*url.URL, error) {
|
||||||
version := defaultVersionFor(config)
|
|
||||||
// TODO: move the default to secure when the apiserver supports TLS by default
|
// 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."
|
// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
|
||||||
defaultTLS := config.CertFile != "" || config.Insecure
|
defaultTLS := config.CertFile != "" || config.Insecure
|
||||||
@ -239,7 +270,7 @@ func defaultServerUrlFor(config *Config) (*url.URL, error) {
|
|||||||
if host == "" {
|
if host == "" {
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
}
|
}
|
||||||
return DefaultServerURL(host, config.Prefix, version, defaultTLS)
|
return DefaultServerURL(host, config.Prefix, config.Version, defaultTLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultVersionFor is shared between defaultServerUrlFor and RESTClientFor
|
// defaultVersionFor is shared between defaultServerUrlFor and RESTClientFor
|
||||||
@ -252,9 +283,3 @@ func defaultVersionFor(config *Config) string {
|
|||||||
}
|
}
|
||||||
return version
|
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")
|
|
||||||
}
|
|
||||||
|
@ -85,6 +85,10 @@ func TestIsConfigTransportTLS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, testCase := range testCases {
|
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)
|
useTLS := IsConfigTransportTLS(testCase.Config)
|
||||||
if testCase.TransportTLS != useTLS {
|
if testCase.TransportTLS != useTLS {
|
||||||
t.Errorf("expected %v for %#v", testCase.TransportTLS, testCase.Config)
|
t.Errorf("expected %v for %#v", testCase.TransportTLS, testCase.Config)
|
||||||
|
@ -36,17 +36,16 @@ type NodeInterface interface {
|
|||||||
// nodes implements NodesInterface
|
// nodes implements NodesInterface
|
||||||
type nodes struct {
|
type nodes struct {
|
||||||
r *Client
|
r *Client
|
||||||
preV1Beta3 bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newNodes returns a nodes object. Uses "minions" as the
|
// newNodes returns a nodes object. Uses "minions" as the
|
||||||
// URL resource name for v1beta1 and v1beta2.
|
// URL resource name for v1beta1 and v1beta2.
|
||||||
func newNodes(c *Client, isPreV1Beta3 bool) *nodes {
|
func newNodes(c *Client) *nodes {
|
||||||
return &nodes{c, isPreV1Beta3}
|
return &nodes{c}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *nodes) resourceName() string {
|
func (c *nodes) resourceName() string {
|
||||||
if c.preV1Beta3 {
|
if preV1Beta3(c.r.APIVersion()) {
|
||||||
return "minions"
|
return "minions"
|
||||||
}
|
}
|
||||||
return "nodes"
|
return "nodes"
|
||||||
|
@ -89,8 +89,12 @@ type Request struct {
|
|||||||
// whether to poll.
|
// whether to poll.
|
||||||
poller PollFunc
|
poller PollFunc
|
||||||
|
|
||||||
// If true, put ns/<namespace> in path; if false, add "?namespace=<namespace>" as a query parameter
|
// If true, add "?namespace=<namespace>" as a query parameter, if false put ns/<namespace> in path
|
||||||
namespaceInPath bool
|
// 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
|
// generic components accessible via method setters
|
||||||
path string
|
path string
|
||||||
@ -110,15 +114,18 @@ type Request struct {
|
|||||||
body io.Reader
|
body io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest creates a new request with the core attributes.
|
// 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, namespaceInPath bool) *Request {
|
func NewRequest(client HTTPClient, verb string, baseURL *url.URL,
|
||||||
|
codec runtime.Codec, namespaceInQuery bool, preserveResourceCase bool) *Request {
|
||||||
return &Request{
|
return &Request{
|
||||||
client: client,
|
client: client,
|
||||||
verb: verb,
|
verb: verb,
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
codec: codec,
|
|
||||||
namespaceInPath: namespaceInPath,
|
|
||||||
path: baseURL.Path,
|
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 {
|
func (r *Request) finalURL() string {
|
||||||
p := r.path
|
p := r.path
|
||||||
if r.namespaceInPath {
|
if !r.namespaceInQuery {
|
||||||
p = path.Join(p, "ns", r.namespace)
|
p = path.Join(p, "ns", r.namespace)
|
||||||
}
|
}
|
||||||
if len(r.resource) != 0 {
|
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
|
// 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 {
|
if len(r.resourceName) != 0 || len(r.subpath) != 0 {
|
||||||
@ -342,7 +353,7 @@ func (r *Request) finalURL() string {
|
|||||||
query.Add(key, value)
|
query.Add(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.namespaceInPath && len(r.namespace) > 0 {
|
if r.namespaceInQuery && len(r.namespace) > 0 {
|
||||||
query.Add("namespace", r.namespace)
|
query.Add("namespace", r.namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,21 +75,21 @@ func TestRequestWithErrorWontChange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestPreservesBaseTrailingSlash(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/" {
|
if s := r.finalURL(); s != "/path/" {
|
||||||
t.Errorf("trailing slash should be preserved: %s", s)
|
t.Errorf("trailing slash should be preserved: %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestAbsPathPreservesTrailingSlash(t *testing.T) {
|
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/" {
|
if s := r.finalURL(); s != "/foo/" {
|
||||||
t.Errorf("trailing slash should be preserved: %s", s)
|
t.Errorf("trailing slash should be preserved: %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestAbsPathJoins(t *testing.T) {
|
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" {
|
if s := r.finalURL(); s != "foo/bar/baz" {
|
||||||
t.Errorf("trailing slash should be preserved: %s", s)
|
t.Errorf("trailing slash should be preserved: %s", s)
|
||||||
}
|
}
|
||||||
@ -100,6 +100,7 @@ func TestRequestSetsNamespace(t *testing.T) {
|
|||||||
baseURL: &url.URL{
|
baseURL: &url.URL{
|
||||||
Path: "/",
|
Path: "/",
|
||||||
},
|
},
|
||||||
|
namespaceInQuery: true,
|
||||||
}).Namespace("foo")
|
}).Namespace("foo")
|
||||||
if r.namespace == "" {
|
if r.namespace == "" {
|
||||||
t.Errorf("namespace should be set: %#v", r)
|
t.Errorf("namespace should be set: %#v", r)
|
||||||
@ -112,7 +113,6 @@ func TestRequestSetsNamespace(t *testing.T) {
|
|||||||
baseURL: &url.URL{
|
baseURL: &url.URL{
|
||||||
Path: "/",
|
Path: "/",
|
||||||
},
|
},
|
||||||
namespaceInPath: true,
|
|
||||||
}).Namespace("foo")
|
}).Namespace("foo")
|
||||||
if s := r.finalURL(); s != "ns/foo" {
|
if s := r.finalURL(); s != "ns/foo" {
|
||||||
t.Errorf("namespace should be in path: %s", s)
|
t.Errorf("namespace should be in path: %s", s)
|
||||||
@ -123,7 +123,6 @@ func TestRequestOrdersNamespaceInPath(t *testing.T) {
|
|||||||
r := (&Request{
|
r := (&Request{
|
||||||
baseURL: &url.URL{},
|
baseURL: &url.URL{},
|
||||||
path: "/test/",
|
path: "/test/",
|
||||||
namespaceInPath: true,
|
|
||||||
}).Name("bar").Resource("baz").Namespace("foo")
|
}).Name("bar").Resource("baz").Namespace("foo")
|
||||||
if s := r.finalURL(); s != "/test/ns/foo/baz/bar" {
|
if s := r.finalURL(); s != "/test/ns/foo/baz/bar" {
|
||||||
t.Errorf("namespace should be in order in path: %s", s)
|
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},
|
{Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
|
||||||
}
|
}
|
||||||
for i, test := range testCases {
|
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 {
|
if test.Response.Body == nil {
|
||||||
test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,13 @@ import (
|
|||||||
// Most consumers should use client.New() to get a Kubernetes API client.
|
// Most consumers should use client.New() to get a Kubernetes API client.
|
||||||
type RESTClient struct {
|
type RESTClient struct {
|
||||||
baseURL *url.URL
|
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
|
// LegacyBehavior controls if URLs should encode the namespace as a query param,
|
||||||
// needed for backward compatibility
|
// and if resource case is preserved for supporting older API conventions of
|
||||||
namespaceInPath bool
|
// Kubernetes. Newer clients should leave this false.
|
||||||
|
LegacyBehavior bool
|
||||||
|
|
||||||
// Codec is the encoding and decoding scheme that applies to a particular set of
|
// Codec is the encoding and decoding scheme that applies to a particular set of
|
||||||
// REST resources.
|
// REST resources.
|
||||||
@ -59,10 +62,9 @@ type RESTClient struct {
|
|||||||
|
|
||||||
// NewRESTClient creates a new RESTClient. This client performs generic REST functions
|
// 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
|
// 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
|
// decoding of responses from the server. If this client should use the older, legacy
|
||||||
// of the path (after the resource), set namespaceInPath to true, otherwise it will be
|
// API conventions from Kubernetes API v1beta1 and v1beta2, set legacyBehavior true.
|
||||||
// passed as "namespace" in the query string.
|
func NewRESTClient(baseURL *url.URL, apiVersion string, c runtime.Codec, legacyBehavior bool) *RESTClient {
|
||||||
func NewRESTClient(baseURL *url.URL, c runtime.Codec, namespaceInPath bool) *RESTClient {
|
|
||||||
base := *baseURL
|
base := *baseURL
|
||||||
if !strings.HasSuffix(base.Path, "/") {
|
if !strings.HasSuffix(base.Path, "/") {
|
||||||
base.Path += "/"
|
base.Path += "/"
|
||||||
@ -72,9 +74,11 @@ func NewRESTClient(baseURL *url.URL, c runtime.Codec, namespaceInPath bool) *RES
|
|||||||
|
|
||||||
return &RESTClient{
|
return &RESTClient{
|
||||||
baseURL: &base,
|
baseURL: &base,
|
||||||
|
apiVersion: apiVersion,
|
||||||
|
|
||||||
Codec: c,
|
Codec: c,
|
||||||
|
|
||||||
namespaceInPath: namespaceInPath,
|
LegacyBehavior: legacyBehavior,
|
||||||
|
|
||||||
// Make asynchronous requests by default
|
// Make asynchronous requests by default
|
||||||
Sync: false,
|
Sync: false,
|
||||||
@ -106,7 +110,7 @@ func (c *RESTClient) Verb(verb string) *Request {
|
|||||||
if poller == nil {
|
if poller == nil {
|
||||||
poller = c.DefaultPoll
|
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").
|
// 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()
|
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) {
|
func (c *RESTClient) DefaultPoll(name string) (*Request, bool) {
|
||||||
if c.PollPeriod == 0 {
|
if c.PollPeriod == 0 {
|
||||||
return nil, false
|
return nil, false
|
||||||
@ -143,3 +148,8 @@ func (c *RESTClient) DefaultPoll(name string) (*Request, bool) {
|
|||||||
// Make a poll request
|
// Make a poll request
|
||||||
return c.Operation(name).Poller(c.DefaultPoll), true
|
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
|
||||||
|
}
|
||||||
|
@ -31,19 +31,19 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChecksCodec(t *testing.T) {
|
func TestSetsCodec(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
Err bool
|
Err bool
|
||||||
Prefix string
|
Prefix string
|
||||||
Codec runtime.Codec
|
Codec runtime.Codec
|
||||||
}{
|
}{
|
||||||
"v1beta1": {false, "/v1beta1/", v1beta1.Codec},
|
"v1beta1": {false, "/api/v1beta1/", v1beta1.Codec},
|
||||||
"": {false, "/v1beta1/", v1beta1.Codec},
|
"": {false, "/api/v1beta1/", v1beta1.Codec},
|
||||||
"v1beta2": {false, "/v1beta2/", v1beta2.Codec},
|
"v1beta2": {false, "/api/v1beta2/", v1beta2.Codec},
|
||||||
"v1beta3": {true, "", nil},
|
"v1beta3": {true, "", nil},
|
||||||
}
|
}
|
||||||
for version, expected := range testCases {
|
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 {
|
switch {
|
||||||
case err == nil && expected.Err:
|
case err == nil && expected.Err:
|
||||||
t.Errorf("expected error but was nil")
|
t.Errorf("expected error but was nil")
|
||||||
@ -54,15 +54,70 @@ func TestChecksCodec(t *testing.T) {
|
|||||||
case err != nil:
|
case err != nil:
|
||||||
continue
|
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)
|
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)
|
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) {
|
func TestValidatesHostParameter(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
Host string
|
Host string
|
||||||
@ -81,7 +136,7 @@ func TestValidatesHostParameter(t *testing.T) {
|
|||||||
{"host/server", "", "", true},
|
{"host/server", "", "", true},
|
||||||
}
|
}
|
||||||
for i, testCase := range testCases {
|
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 {
|
switch {
|
||||||
case err == nil && testCase.Err:
|
case err == nil && testCase.Err:
|
||||||
t.Errorf("expected error but was nil")
|
t.Errorf("expected error but was nil")
|
||||||
@ -110,7 +165,14 @@ func TestDoRequestBearer(t *testing.T) {
|
|||||||
testServer := httptest.NewServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
request, _ := http.NewRequest("GET", testServer.URL, nil)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -133,7 +195,14 @@ func TestDoRequestAccepted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testServer := httptest.NewServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
defer testServer.Close()
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -167,7 +236,15 @@ func TestDoRequestAcceptedSuccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testServer := httptest.NewServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
defer testServer.Close()
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -198,7 +275,12 @@ func TestDoRequestFailed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testServer := httptest.NewServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
defer testServer.Close()
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -226,7 +308,15 @@ func TestDoRequestCreated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testServer := httptest.NewServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
defer testServer.Close()
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ func getFakeClient(t *testing.T, validURLs []string) (ClientPosterFunc, *httptes
|
|||||||
return func(mapping *meta.RESTMapping) (RESTClientPoster, error) {
|
return func(mapping *meta.RESTMapping) (RESTClientPoster, error) {
|
||||||
fakeCodec := runtime.CodecFor(api.Scheme, "v1beta1")
|
fakeCodec := runtime.CodecFor(api.Scheme, "v1beta1")
|
||||||
fakeUri, _ := url.Parse(server.URL + "/api/v1beta1")
|
fakeUri, _ := url.Parse(server.URL + "/api/v1beta1")
|
||||||
return client.NewRESTClient(fakeUri, fakeCodec, false), nil
|
return client.NewRESTClient(fakeUri, "v1beta1", fakeCodec, true), nil
|
||||||
}, server
|
}, server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func makeNamespaceURL(namespace, suffix string) string {
|
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("/ns/" + namespace + suffix)
|
||||||
}
|
}
|
||||||
return makeURL(suffix + "?namespace=" + namespace)
|
return makeURL(suffix + "?namespace=" + namespace)
|
||||||
|
@ -88,7 +88,7 @@ func TestPollMinions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeNamespaceURL(namespace, suffix string, isClient bool) string {
|
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)
|
return makeURL("/ns/" + namespace + suffix)
|
||||||
}
|
}
|
||||||
// if this is a url the client should call, encode the url
|
// if this is a url the client should call, encode the url
|
||||||
|
Loading…
Reference in New Issue
Block a user