mirror of https://github.com/k3s-io/k3s
Refactor the client (again) to better support auth
* Allows consumers to provide their own transports for common cases. * Supports KUBE_API_VERSION on test cases for controlling which api version they test against * Provides a common flag registration method for CLIs that need to connect to an API server (to avoid duplicating flags) * Ensures errors are properly returned by the server * Add a Context field to client.Configpull/6/head
parent
88bf01b008
commit
ff2eca97d9
|
@ -27,7 +27,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
|
@ -127,8 +126,12 @@ func main() {
|
|||
Port: *minionPort,
|
||||
}
|
||||
|
||||
ctx := api.NewContext()
|
||||
client, err := client.New(ctx, net.JoinHostPort(*address, strconv.Itoa(int(*port))), *storageVersion, nil)
|
||||
// TODO: expose same flags as client.BindClientConfigFlags but for a server
|
||||
clientConfig := &client.Config{
|
||||
Host: net.JoinHostPort(*address, strconv.Itoa(int(*port))),
|
||||
Version: *storageVersion,
|
||||
}
|
||||
client, err := client.New(clientConfig)
|
||||
if err != nil {
|
||||
glog.Fatalf("Invalid server address: %v", err)
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
||||
|
@ -39,11 +37,15 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
master = flag.String("master", "", "The address of the Kubernetes API server")
|
||||
port = flag.Int("port", masterPkg.ControllerManagerPort, "The port that the controller-manager's http service runs on")
|
||||
address = flag.String("address", "127.0.0.1", "The address to serve from")
|
||||
port = flag.Int("port", masterPkg.ControllerManagerPort, "The port that the controller-manager's http service runs on")
|
||||
address = flag.String("address", "127.0.0.1", "The address to serve from")
|
||||
clientConfig = &client.Config{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
client.BindClientConfigFlags(flag.CommandLine, clientConfig)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
util.InitLogs()
|
||||
|
@ -51,13 +53,13 @@ func main() {
|
|||
|
||||
verflag.PrintAndExitIfRequested()
|
||||
|
||||
if len(*master) == 0 {
|
||||
if len(clientConfig.Host) == 0 {
|
||||
glog.Fatal("usage: controller-manager -master <master>")
|
||||
}
|
||||
ctx := api.NewContext()
|
||||
kubeClient, err := client.New(ctx, *master, latest.OldestVersion, nil)
|
||||
|
||||
kubeClient, err := client.New(clientConfig)
|
||||
if err != nil {
|
||||
glog.Fatalf("Invalid -master: %v", err)
|
||||
glog.Fatalf("Invalid API configuration: %v", err)
|
||||
}
|
||||
|
||||
go http.ListenAndServe(net.JoinHostPort(*address, strconv.Itoa(*port)), nil)
|
||||
|
|
|
@ -29,7 +29,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
|
||||
|
@ -106,7 +108,7 @@ func startComponents(manifestURL string) (apiServerURL string) {
|
|||
}
|
||||
}
|
||||
|
||||
cl := client.NewOrDie(api.NewContext(), apiServer.URL, "", nil)
|
||||
cl := client.NewOrDie(&client.Config{Host: apiServer.URL, Version: testapi.Version()})
|
||||
cl.PollPeriod = time.Second * 1
|
||||
cl.Sync = true
|
||||
|
||||
|
@ -262,12 +264,10 @@ func runAtomicPutTest(c *client.Client) {
|
|||
glog.Infof("Posting update (%s, %s)", l, v)
|
||||
err = c.Put().Path("services").Path(svc.ID).Body(&tmpSvc).Do().Error()
|
||||
if err != nil {
|
||||
if se, ok := err.(*client.StatusErr); ok {
|
||||
if se.Status.Code == http.StatusConflict {
|
||||
glog.Infof("Conflict: (%s, %s)", l, v)
|
||||
// This is what we expect.
|
||||
continue
|
||||
}
|
||||
if errors.IsConflict(err) {
|
||||
glog.Infof("Conflict: (%s, %s)", l, v)
|
||||
// This is what we expect.
|
||||
continue
|
||||
}
|
||||
glog.Errorf("Unexpected error putting atomicService: %v", err)
|
||||
continue
|
||||
|
@ -311,7 +311,7 @@ func main() {
|
|||
// Wait for the synchronization threads to come up.
|
||||
time.Sleep(time.Second * 10)
|
||||
|
||||
kubeClient := client.NewOrDie(api.NewContext(), apiServerURL, "", nil)
|
||||
kubeClient := client.NewOrDie(&client.Config{Host: apiServerURL, Version: testapi.Version()})
|
||||
|
||||
// Run tests in parallel
|
||||
testFuncs := []testFunc{
|
||||
|
|
|
@ -43,7 +43,6 @@ import (
|
|||
var (
|
||||
serverVersion = verflag.Version("server_version", verflag.VersionFalse, "Print the server's version information and quit")
|
||||
preventSkew = flag.Bool("expect_version_match", false, "Fail if server's version doesn't match own version.")
|
||||
httpServer = flag.String("h", "", "The host to connect to.")
|
||||
config = flag.String("c", "", "Path or URL to the config file, or '-' to read from STDIN")
|
||||
selector = flag.String("l", "", "Selector (label query) to use for listing")
|
||||
updatePeriod = flag.Duration("u", 60*time.Second, "Update interval period")
|
||||
|
@ -58,12 +57,17 @@ var (
|
|||
templateFile = flag.String("template_file", "", "If present, load this file as a golang template and use it for output printing")
|
||||
templateStr = flag.String("template", "", "If present, parse this string as a golang template and use it for output printing")
|
||||
imageName = flag.String("image", "", "Image used when updating a replicationController. Will apply to the first container in the pod template.")
|
||||
apiVersion = flag.String("api_version", latest.Version, "The version of the API to use against this server.")
|
||||
caFile = flag.String("certificate_authority", "", "Path to a cert. file for the certificate authority")
|
||||
certFile = flag.String("client_certificate", "", "Path to a client certificate for TLS.")
|
||||
keyFile = flag.String("client_key", "", "Path to a client key file for TLS.")
|
||||
clientConfig = &client.Config{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&clientConfig.Host, "h", "", "The host to connect to.")
|
||||
flag.StringVar(&clientConfig.Version, "api_version", latest.Version, "The version of the API to use against this server.")
|
||||
flag.StringVar(&clientConfig.CAFile, "certificate_authority", "", "Path to a cert. file for the certificate authority")
|
||||
flag.StringVar(&clientConfig.CertFile, "client_certificate", "", "Path to a client certificate for TLS.")
|
||||
flag.StringVar(&clientConfig.KeyFile, "client_key", "", "Path to a client key file for TLS.")
|
||||
}
|
||||
|
||||
var parser = kubecfg.NewParser(map[string]runtime.Object{
|
||||
"pods": &api.Pod{},
|
||||
"services": &api.Service{},
|
||||
|
@ -165,43 +169,29 @@ func main() {
|
|||
|
||||
verflag.PrintAndExitIfRequested()
|
||||
|
||||
var masterServer string
|
||||
if len(*httpServer) > 0 {
|
||||
masterServer = *httpServer
|
||||
} else if len(os.Getenv("KUBERNETES_MASTER")) > 0 {
|
||||
masterServer = os.Getenv("KUBERNETES_MASTER")
|
||||
} else {
|
||||
masterServer = "http://localhost:8080"
|
||||
// Initialize the client
|
||||
if clientConfig.Host == "" {
|
||||
clientConfig.Host = os.Getenv("KUBERNETES_MASTER")
|
||||
}
|
||||
|
||||
// TODO: get the namespace context when kubecfg ns is completed
|
||||
ctx := api.NewContext()
|
||||
clientConfig.Context = api.NewContext()
|
||||
|
||||
kubeClient, err := client.New(ctx, masterServer, *apiVersion, nil)
|
||||
if err != nil {
|
||||
glog.Fatalf("Can't configure client: %v", err)
|
||||
if clientConfig.Host == "" {
|
||||
// TODO: eventually apiserver should start on 443 and be secure by default
|
||||
clientConfig.Host = "http://localhost:8080"
|
||||
}
|
||||
|
||||
// TODO: this won't work if TLS is enabled with client cert auth, but no
|
||||
// passwords are required. Refactor when we address client auth abstraction.
|
||||
if kubeClient.Secure() {
|
||||
if client.IsConfigTransportSecure(clientConfig) {
|
||||
auth, err := kubecfg.LoadAuthInfo(*authConfig, os.Stdin)
|
||||
if err != nil {
|
||||
glog.Fatalf("Error loading auth: %v", err)
|
||||
}
|
||||
if *caFile != "" {
|
||||
auth.CAFile = *caFile
|
||||
}
|
||||
if *certFile != "" {
|
||||
auth.CertFile = *certFile
|
||||
}
|
||||
if *keyFile != "" {
|
||||
auth.KeyFile = *keyFile
|
||||
}
|
||||
kubeClient, err = client.New(ctx, masterServer, *apiVersion, auth)
|
||||
if err != nil {
|
||||
glog.Fatalf("Can't configure client: %v", err)
|
||||
}
|
||||
clientConfig.Username = auth.User
|
||||
clientConfig.Password = auth.Password
|
||||
}
|
||||
kubeClient, err := client.New(clientConfig)
|
||||
if err != nil {
|
||||
glog.Fatalf("Can't configure client: %v", err)
|
||||
}
|
||||
|
||||
if *serverVersion != verflag.VersionFalse {
|
||||
|
|
|
@ -20,8 +20,6 @@ import (
|
|||
"flag"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config"
|
||||
|
@ -33,12 +31,13 @@ import (
|
|||
|
||||
var (
|
||||
configFile = flag.String("configfile", "/tmp/proxy_config", "Configuration file for the proxy")
|
||||
master = flag.String("master", "", "The address of the Kubernetes API server (optional)")
|
||||
etcdServerList util.StringList
|
||||
bindAddress = flag.String("bindaddress", "0.0.0.0", "The address for the proxy server to serve on (set to 0.0.0.0 or \"\" for all interfaces)")
|
||||
clientConfig = &client.Config{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
client.BindClientConfigFlags(flag.CommandLine, clientConfig)
|
||||
flag.Var(&etcdServerList, "etcd_servers", "List of etcd servers to watch (http://ip:port), comma separated (optional)")
|
||||
}
|
||||
|
||||
|
@ -53,13 +52,11 @@ func main() {
|
|||
endpointsConfig := config.NewEndpointsConfig()
|
||||
|
||||
// define api config source
|
||||
if *master != "" {
|
||||
glog.Infof("Using api calls to get config %v", *master)
|
||||
ctx := api.NewContext()
|
||||
//TODO: add auth info
|
||||
client, err := client.New(ctx, *master, latest.OldestVersion, nil)
|
||||
if clientConfig.Host != "" {
|
||||
glog.Infof("Using api calls to get config %v", clientConfig.Host)
|
||||
client, err := client.New(clientConfig)
|
||||
if err != nil {
|
||||
glog.Fatalf("Invalid -master: %v", err)
|
||||
glog.Fatalf("Invalid API configuration: %v", err)
|
||||
}
|
||||
config.NewSourceAPI(
|
||||
client,
|
||||
|
@ -70,7 +67,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Create a configuration source that handles configuration from etcd.
|
||||
if len(etcdServerList) > 0 && *master == "" {
|
||||
if len(etcdServerList) > 0 && clientConfig.Host == "" {
|
||||
glog.Infof("Using etcd servers %v", etcdServerList)
|
||||
|
||||
// Set up logger for etcd client
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package testapi provides a helper for retrieving the KUBE_API_VERSION environment variable.
|
||||
package testapi
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// Version returns the API version to test against as set by the KUBE_API_VERSION env var.
|
||||
func Version() string {
|
||||
version := os.Getenv("KUBE_API_VERSION")
|
||||
if version == "" {
|
||||
version = latest.Version
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
func CodecForVersionOrDie() runtime.Codec {
|
||||
interfaces, err := latest.InterfacesFor(Version())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return interfaces.Codec
|
||||
}
|
|
@ -17,20 +17,11 @@ limitations under the License.
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
)
|
||||
|
@ -91,207 +82,17 @@ type MinionInterface interface {
|
|||
ListMinions() (*api.MinionList, error)
|
||||
}
|
||||
|
||||
// Client is the actual implementation of a Kubernetes client.
|
||||
// APIStatus is exposed by errors that can be converted to an api.Status object
|
||||
// for finer grained details.
|
||||
type APIStatus interface {
|
||||
Status() api.Status
|
||||
}
|
||||
|
||||
// Client is the implementation of a Kubernetes client.
|
||||
type Client struct {
|
||||
*RESTClient
|
||||
}
|
||||
|
||||
// New creates a Kubernetes client. This client works with pods, replication controllers
|
||||
// and services. It allows operations such as list, get, update and delete on these objects.
|
||||
// host must be a host string, a host:port combo, or an http or https URL. Passing a prefix
|
||||
// to a URL will prepend the server path. The API version to use may be specified or left
|
||||
// empty to use the client preferred version. Returns an error if host cannot be converted to
|
||||
// a valid URL.
|
||||
func New(ctx api.Context, host, version string, auth *AuthInfo) (*Client, error) {
|
||||
if version == "" {
|
||||
// Clients default to the preferred code API version
|
||||
// TODO: implement version negotation (highest version supported by server)
|
||||
version = latest.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, ", "))
|
||||
}
|
||||
prefix := fmt.Sprintf("/api/%s/", version)
|
||||
restClient, err := NewRESTClient(ctx, host, auth, prefix, versionInterfaces.Codec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("API URL '%s' is not valid: %v", host, err)
|
||||
}
|
||||
return &Client{restClient}, nil
|
||||
}
|
||||
|
||||
// NewOrDie creates a Kubernetes client and panics if the provided host is invalid.
|
||||
func NewOrDie(ctx api.Context, host, version string, auth *AuthInfo) *Client {
|
||||
client, err := New(ctx, host, version, auth)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// StatusErr might get returned from an api call if your request is still being processed
|
||||
// and hence the expected return data is not available yet.
|
||||
type StatusErr struct {
|
||||
Status api.Status
|
||||
}
|
||||
|
||||
func (s *StatusErr) Error() string {
|
||||
return fmt.Sprintf("Status: %v (%#v)", s.Status.Status, s.Status)
|
||||
}
|
||||
|
||||
// AuthInfo is used to store authorization information.
|
||||
type AuthInfo struct {
|
||||
User string
|
||||
Password string
|
||||
CAFile string
|
||||
CertFile string
|
||||
KeyFile string
|
||||
}
|
||||
|
||||
// RESTClient holds common code used to work with API resources that follow the
|
||||
// Kubernetes API pattern.
|
||||
// Host is the http://... base for the URL
|
||||
type RESTClient struct {
|
||||
ctx api.Context
|
||||
host string
|
||||
prefix string
|
||||
secure bool
|
||||
auth *AuthInfo
|
||||
httpClient *http.Client
|
||||
Sync bool
|
||||
PollPeriod time.Duration
|
||||
Timeout time.Duration
|
||||
Codec runtime.Codec
|
||||
}
|
||||
|
||||
// NewRESTClient creates a new RESTClient. This client performs generic REST functions
|
||||
// such as Get, Put, Post, and Delete on specified paths.
|
||||
func NewRESTClient(ctx api.Context, host string, auth *AuthInfo, path string, c runtime.Codec) (*RESTClient, error) {
|
||||
prefix, err := normalizePrefix(host, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base := *prefix
|
||||
base.Path = ""
|
||||
base.RawQuery = ""
|
||||
base.Fragment = ""
|
||||
|
||||
var config *tls.Config
|
||||
if auth != nil && len(auth.CertFile) != 0 {
|
||||
cert, err := tls.LoadX509KeyPair(auth.CertFile, auth.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := ioutil.ReadFile(auth.CAFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AppendCertsFromPEM(data)
|
||||
config = &tls.Config{
|
||||
Certificates: []tls.Certificate{
|
||||
cert,
|
||||
},
|
||||
RootCAs: certPool,
|
||||
ClientCAs: certPool,
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
}
|
||||
} else {
|
||||
config = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
|
||||
return &RESTClient{
|
||||
ctx: ctx,
|
||||
host: base.String(),
|
||||
prefix: prefix.Path,
|
||||
secure: prefix.Scheme == "https",
|
||||
auth: auth,
|
||||
httpClient: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: config,
|
||||
},
|
||||
},
|
||||
Sync: false,
|
||||
PollPeriod: time.Second * 2,
|
||||
Timeout: time.Second * 20,
|
||||
Codec: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// normalizePrefix ensures the passed initial value is valid.
|
||||
func normalizePrefix(host, prefix string) (*url.URL, error) {
|
||||
if host == "" {
|
||||
return nil, fmt.Errorf("host must be a URL or a host:port pair")
|
||||
}
|
||||
base := host
|
||||
hostURL, err := url.Parse(base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hostURL.Scheme == "" {
|
||||
hostURL, err = url.Parse("http://" + base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hostURL.Path != "" && hostURL.Path != "/" {
|
||||
return nil, fmt.Errorf("host must be a URL or a host:port pair: %s", base)
|
||||
}
|
||||
}
|
||||
hostURL.Path += prefix
|
||||
|
||||
return hostURL, nil
|
||||
}
|
||||
|
||||
// Secure returns true if the client is configured for secure connections.
|
||||
func (c *RESTClient) Secure() bool {
|
||||
return c.secure
|
||||
}
|
||||
|
||||
// doRequest executes a request, adds authentication (if auth != nil), and HTTPS
|
||||
// cert ignoring.
|
||||
func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
|
||||
if c.auth != nil {
|
||||
request.SetBasicAuth(c.auth.User, c.auth.Password)
|
||||
}
|
||||
response, err := c.httpClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return body, err
|
||||
}
|
||||
|
||||
// Did the server give us a status response?
|
||||
isStatusResponse := false
|
||||
var status api.Status
|
||||
if err := latest.Codec.DecodeInto(body, &status); err == nil && status.Status != "" {
|
||||
isStatusResponse = true
|
||||
}
|
||||
|
||||
switch {
|
||||
case response.StatusCode == http.StatusConflict:
|
||||
// Return error given by server, if there was one.
|
||||
if isStatusResponse {
|
||||
return nil, &StatusErr{status}
|
||||
}
|
||||
fallthrough
|
||||
case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent:
|
||||
return nil, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
|
||||
}
|
||||
|
||||
// If the server gave us a status back, look at what it was.
|
||||
if isStatusResponse && status.Status != api.StatusSuccess {
|
||||
// "Working" requests need to be handled specially.
|
||||
// "Failed" requests are clearly just an error and it makes sense to return them as such.
|
||||
return nil, &StatusErr{status}
|
||||
}
|
||||
return body, err
|
||||
}
|
||||
|
||||
// ListPods takes a selector, and returns the list of pods that match that selector.
|
||||
func (c *Client) ListPods(selector labels.Selector) (result *api.PodList, err error) {
|
||||
result = &api.PodList{}
|
||||
|
|
|
@ -27,8 +27,6 @@ import (
|
|||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
|
@ -38,73 +36,112 @@ import (
|
|||
// TODO: Move this to a common place, it's needed in multiple tests.
|
||||
const apiPath = "/api/v1beta1"
|
||||
|
||||
func TestChecksCodec(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
Err bool
|
||||
Prefix string
|
||||
Codec runtime.Codec
|
||||
}{
|
||||
"v1beta1": {false, "/api/v1beta1/", v1beta1.Codec},
|
||||
"": {false, "/api/v1beta1/", v1beta1.Codec},
|
||||
"v1beta2": {false, "/api/v1beta2/", v1beta2.Codec},
|
||||
"v1beta3": {true, "", nil},
|
||||
type testRequest struct {
|
||||
Method string
|
||||
Path string
|
||||
Header string
|
||||
Query url.Values
|
||||
Body runtime.Object
|
||||
RawBody *string
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
StatusCode int
|
||||
Body runtime.Object
|
||||
RawBody *string
|
||||
}
|
||||
|
||||
type testClient struct {
|
||||
*Client
|
||||
Request testRequest
|
||||
Response Response
|
||||
Error bool
|
||||
server *httptest.Server
|
||||
handler *util.FakeHandler
|
||||
// For query args, an optional function to validate the contents
|
||||
// useful when the contents can change but still be correct.
|
||||
// Maps from query arg key to validator.
|
||||
// If no validator is present, string equality is used.
|
||||
QueryValidator map[string]func(string, string) bool
|
||||
}
|
||||
|
||||
func (c *testClient) Setup() *testClient {
|
||||
c.handler = &util.FakeHandler{
|
||||
StatusCode: c.Response.StatusCode,
|
||||
}
|
||||
ctx := api.NewContext()
|
||||
for version, expected := range testCases {
|
||||
client, err := New(ctx, "127.0.0.1", version, nil)
|
||||
switch {
|
||||
case err == nil && expected.Err:
|
||||
t.Errorf("expected error but was nil")
|
||||
continue
|
||||
case err != nil && !expected.Err:
|
||||
t.Errorf("unexpected error %v", err)
|
||||
continue
|
||||
case err != nil:
|
||||
continue
|
||||
}
|
||||
if e, a := expected.Prefix, client.prefix; e != a {
|
||||
t.Errorf("expected %#v, got %#v", e, a)
|
||||
}
|
||||
if e, a := expected.Codec, client.Codec; e != a {
|
||||
t.Errorf("expected %#v, got %#v", e, a)
|
||||
}
|
||||
if responseBody := body(c.Response.Body, c.Response.RawBody); responseBody != nil {
|
||||
c.handler.ResponseBody = *responseBody
|
||||
}
|
||||
c.server = httptest.NewServer(c.handler)
|
||||
if c.Client == nil {
|
||||
c.Client = NewOrDie(&Config{
|
||||
Host: c.server.URL,
|
||||
Version: "v1beta1",
|
||||
})
|
||||
}
|
||||
c.QueryValidator = map[string]func(string, string) bool{}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) {
|
||||
c.ValidateCommon(t, err)
|
||||
|
||||
if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
|
||||
t.Errorf("bad response for request %#v: expected %s, got %s", c.Request, c.Response.Body, received)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatesHostParameter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
Host string
|
||||
Prefix string
|
||||
Err bool
|
||||
}{
|
||||
"127.0.0.1": {"http://127.0.0.1", "/api/v1beta1/", false},
|
||||
"127.0.0.1:8080": {"http://127.0.0.1:8080", "/api/v1beta1/", false},
|
||||
"foo.bar.com": {"http://foo.bar.com", "/api/v1beta1/", false},
|
||||
"http://host/server": {"http://host", "/server/api/v1beta1/", false},
|
||||
"host/server": {"", "", true},
|
||||
func (c *testClient) ValidateRaw(t *testing.T, received []byte, err error) {
|
||||
c.ValidateCommon(t, err)
|
||||
|
||||
if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
|
||||
t.Errorf("bad response for request %#v: expected %s, got %s", c.Request, c.Response.Body, received)
|
||||
}
|
||||
ctx := api.NewContext()
|
||||
for k, expected := range testCases {
|
||||
c, err := NewRESTClient(ctx, k, nil, "/api/v1beta1/", v1beta1.Codec)
|
||||
switch {
|
||||
case err == nil && expected.Err:
|
||||
t.Errorf("expected error but was nil")
|
||||
continue
|
||||
case err != nil && !expected.Err:
|
||||
t.Errorf("unexpected error %v", err)
|
||||
continue
|
||||
case err != nil:
|
||||
continue
|
||||
}
|
||||
|
||||
func (c *testClient) ValidateCommon(t *testing.T, err error) {
|
||||
defer c.server.Close()
|
||||
|
||||
if c.Error {
|
||||
if err == nil {
|
||||
t.Errorf("error expected for %#v, got none", c.Request)
|
||||
}
|
||||
if e, a := expected.Host, c.host; e != a {
|
||||
t.Errorf("%s: expected host %s, got %s", k, e, a)
|
||||
continue
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("no error expected for %#v, got: %v", c.Request, err)
|
||||
}
|
||||
|
||||
if c.handler.RequestReceived == nil {
|
||||
t.Errorf("handler had an empty request, %#v", c)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody := body(c.Request.Body, c.Request.RawBody)
|
||||
actualQuery := c.handler.RequestReceived.URL.Query()
|
||||
// We check the query manually, so blank it out so that FakeHandler.ValidateRequest
|
||||
// won't check it.
|
||||
c.handler.RequestReceived.URL.RawQuery = ""
|
||||
c.handler.ValidateRequest(t, path.Join(apiPath, c.Request.Path), c.Request.Method, requestBody)
|
||||
for key, values := range c.Request.Query {
|
||||
validator, ok := c.QueryValidator[key]
|
||||
if !ok {
|
||||
validator = func(a, b string) bool { return a == b }
|
||||
}
|
||||
if e, a := expected.Prefix, c.prefix; e != a {
|
||||
t.Errorf("%s: expected prefix %s, got %s", k, e, a)
|
||||
continue
|
||||
observed := actualQuery.Get(key)
|
||||
if !validator(values[0], observed) {
|
||||
t.Errorf("Unexpected query arg for key: %s. Expected %s, Received %s", key, values[0], observed)
|
||||
}
|
||||
}
|
||||
if c.Request.Header != "" {
|
||||
if c.handler.RequestReceived.Header.Get(c.Request.Header) == "" {
|
||||
t.Errorf("header %q not found in request %#v", c.Request.Header, c.handler.RequestReceived)
|
||||
}
|
||||
}
|
||||
|
||||
if expected, received := requestBody, c.handler.RequestBody; expected != nil && *expected != received {
|
||||
t.Errorf("bad body for request %#v: expected %s, got %s", c.Request, *expected, received)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEmptyPods(t *testing.T) {
|
||||
|
@ -353,109 +390,6 @@ func body(obj runtime.Object, raw *string) *string {
|
|||
return raw
|
||||
}
|
||||
|
||||
type testRequest struct {
|
||||
Method string
|
||||
Path string
|
||||
Header string
|
||||
Query url.Values
|
||||
Body runtime.Object
|
||||
RawBody *string
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
StatusCode int
|
||||
Body runtime.Object
|
||||
RawBody *string
|
||||
}
|
||||
|
||||
type testClient struct {
|
||||
*Client
|
||||
Request testRequest
|
||||
Response Response
|
||||
Error bool
|
||||
server *httptest.Server
|
||||
handler *util.FakeHandler
|
||||
// For query args, an optional function to validate the contents
|
||||
// useful when the contents can change but still be correct.
|
||||
// Maps from query arg key to validator.
|
||||
// If no validator is present, string equality is used.
|
||||
QueryValidator map[string]func(string, string) bool
|
||||
}
|
||||
|
||||
func (c *testClient) Setup() *testClient {
|
||||
ctx := api.NewContext()
|
||||
c.handler = &util.FakeHandler{
|
||||
StatusCode: c.Response.StatusCode,
|
||||
}
|
||||
if responseBody := body(c.Response.Body, c.Response.RawBody); responseBody != nil {
|
||||
c.handler.ResponseBody = *responseBody
|
||||
}
|
||||
c.server = httptest.NewServer(c.handler)
|
||||
if c.Client == nil {
|
||||
c.Client = NewOrDie(ctx, "localhost", "v1beta1", nil)
|
||||
}
|
||||
c.Client.host = c.server.URL
|
||||
c.Client.prefix = "/api/v1beta1/"
|
||||
c.QueryValidator = map[string]func(string, string) bool{}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) {
|
||||
c.ValidateCommon(t, err)
|
||||
|
||||
if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
|
||||
t.Errorf("bad response for request %#v: expected %s, got %s", c.Request, c.Response.Body, received)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testClient) ValidateRaw(t *testing.T, received []byte, err error) {
|
||||
c.ValidateCommon(t, err)
|
||||
|
||||
if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
|
||||
t.Errorf("bad response for request %#v: expected %s, got %s", c.Request, c.Response.Body, received)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testClient) ValidateCommon(t *testing.T, err error) {
|
||||
defer c.server.Close()
|
||||
|
||||
if c.Error {
|
||||
if err == nil {
|
||||
t.Errorf("error expected for %#v, got none", c.Request)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("no error expected for %#v, got: %v", c.Request, err)
|
||||
}
|
||||
|
||||
requestBody := body(c.Request.Body, c.Request.RawBody)
|
||||
actualQuery := c.handler.RequestReceived.URL.Query()
|
||||
// We check the query manually, so blank it out so that FakeHandler.ValidateRequest
|
||||
// won't check it.
|
||||
c.handler.RequestReceived.URL.RawQuery = ""
|
||||
c.handler.ValidateRequest(t, path.Join(apiPath, c.Request.Path), c.Request.Method, requestBody)
|
||||
for key, values := range c.Request.Query {
|
||||
validator, ok := c.QueryValidator[key]
|
||||
if !ok {
|
||||
validator = func(a, b string) bool { return a == b }
|
||||
}
|
||||
observed := actualQuery.Get(key)
|
||||
if !validator(values[0], observed) {
|
||||
t.Errorf("Unexpected query arg for key: %s. Expected %s, Received %s", key, values[0], observed)
|
||||
}
|
||||
}
|
||||
if c.Request.Header != "" {
|
||||
if c.handler.RequestReceived.Header.Get(c.Request.Header) == "" {
|
||||
t.Errorf("header %q not found in request %#v", c.Request.Header, c.handler.RequestReceived)
|
||||
}
|
||||
}
|
||||
|
||||
if expected, received := requestBody, c.handler.RequestBody; expected != nil && *expected != received {
|
||||
t.Errorf("bad body for request %#v: expected %s, got %s", c.Request, *expected, received)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListServices(t *testing.T) {
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/services"},
|
||||
|
@ -517,10 +451,10 @@ func TestGetService(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCreateService(t *testing.T) {
|
||||
c := (&testClient{
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "POST", Path: "/services", Body: &api.Service{JSONBase: api.JSONBase{ID: "service-1"}}},
|
||||
Response: Response{StatusCode: 200, Body: &api.Service{JSONBase: api.JSONBase{ID: "service-1"}}},
|
||||
}).Setup()
|
||||
}
|
||||
response, err := c.Setup().CreateService(&api.Service{JSONBase: api.JSONBase{ID: "service-1"}})
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
@ -571,102 +505,6 @@ func TestGetEndpoints(t *testing.T) {
|
|||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
func TestDoRequest(t *testing.T) {
|
||||
invalid := "aaaaa"
|
||||
testClients := []testClient{
|
||||
{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 200}},
|
||||
{Request: testRequest{Method: "GET", Path: "bad%ZZ"}, Error: true},
|
||||
{Client: NewOrDie(api.NewContext(), "localhost", "v1beta1", &AuthInfo{"foo", "bar", "", "", ""}), Request: testRequest{Method: "GET", Path: "auth", Header: "Authorization"}, Response: Response{StatusCode: 200}},
|
||||
{Client: &Client{&RESTClient{httpClient: http.DefaultClient}}, Request: testRequest{Method: "GET", Path: "nocertificate"}, Error: true},
|
||||
{Request: testRequest{Method: "GET", Path: "error"}, Response: Response{StatusCode: 500}, Error: true},
|
||||
{Request: testRequest{Method: "POST", Path: "faildecode"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
|
||||
{Request: testRequest{Method: "GET", Path: "failread"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
|
||||
}
|
||||
for _, c := range testClients {
|
||||
client := c.Setup()
|
||||
prefix, _ := url.Parse(client.host)
|
||||
prefix.Path = client.prefix + c.Request.Path
|
||||
request := &http.Request{
|
||||
Method: c.Request.Method,
|
||||
Header: make(http.Header),
|
||||
URL: prefix,
|
||||
}
|
||||
response, err := client.doRequest(request)
|
||||
c.ValidateRaw(t, response, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRequestAccepted(t *testing.T) {
|
||||
status := &api.Status{Status: api.StatusWorking}
|
||||
expectedBody, _ := latest.Codec.Encode(status)
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 202,
|
||||
ResponseBody: string(expectedBody),
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
auth := AuthInfo{User: "user", Password: "pass"}
|
||||
ctx := api.NewContext()
|
||||
c, err := New(ctx, testServer.URL, "", &auth)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, err := c.doRequest(request)
|
||||
if request.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("Unexpected non-error")
|
||||
return
|
||||
}
|
||||
se, ok := err.(*StatusErr)
|
||||
if !ok {
|
||||
t.Errorf("Unexpected kind of error: %#v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(&se.Status, status) {
|
||||
t.Errorf("Unexpected status: %#v", se.Status)
|
||||
}
|
||||
if body != nil {
|
||||
t.Errorf("Expected nil body, but saw: '%s'", body)
|
||||
}
|
||||
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
|
||||
}
|
||||
|
||||
func TestDoRequestAcceptedSuccess(t *testing.T) {
|
||||
status := &api.Status{Status: api.StatusSuccess}
|
||||
expectedBody, _ := latest.Codec.Encode(status)
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 202,
|
||||
ResponseBody: string(expectedBody),
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
auth := AuthInfo{User: "user", Password: "pass"}
|
||||
ctx := api.NewContext()
|
||||
c, err := New(ctx, testServer.URL, "", &auth)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, err := c.doRequest(request)
|
||||
if request.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
statusOut, err := latest.Codec.Decode(body)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(status, statusOut) {
|
||||
t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, statusOut)
|
||||
}
|
||||
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
|
||||
}
|
||||
|
||||
func TestGetServerVersion(t *testing.T) {
|
||||
expect := version.Info{
|
||||
Major: "foo",
|
||||
|
@ -683,7 +521,7 @@ func TestGetServerVersion(t *testing.T) {
|
|||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}))
|
||||
client := NewOrDie(api.NewContext(), server.URL, "", nil)
|
||||
client := NewOrDie(&Config{Host: server.URL})
|
||||
|
||||
got, err := client.ServerVersion()
|
||||
if err != nil {
|
||||
|
|
|
@ -14,6 +14,34 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package client contains the implementation of the client side communication with the
|
||||
// Kubernetes master.
|
||||
/*
|
||||
Package client contains the implementation of the client side communication with the
|
||||
Kubernetes master. The Client class provides methods for reading, creating, updating,
|
||||
and deleting pods, replication controllers, services, and minions.
|
||||
|
||||
Most consumers should use the Config object to create a Client:
|
||||
|
||||
config := &client.Config{
|
||||
Host: "http://localhost:8080",
|
||||
Username: "test",
|
||||
Password: "password",
|
||||
}
|
||||
client, err := client.New(&config)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
client.ListPods()
|
||||
|
||||
More advanced consumers may wish to provide their own transport via a http.RoundTripper:
|
||||
|
||||
config := &client.Config{
|
||||
Host: "https://localhost:8080",
|
||||
Transport: oauthclient.Transport(),
|
||||
}
|
||||
client, err := client.New(&config)
|
||||
|
||||
The RESTClient type implements the Kubernetes API conventions (see `docs/api-conventions.md`)
|
||||
for a given API path and is intended for use by consumers implementing their own Kubernetes
|
||||
compatible APIs.
|
||||
*/
|
||||
package client
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
// FlagSet abstracts the flag interface for compatibility with both Golang "flag"
|
||||
// and cobra pflags (Posix style).
|
||||
type FlagSet interface {
|
||||
StringVar(p *string, name, value, usage string)
|
||||
}
|
||||
|
||||
// BindClientConfigFlags registers a standard set of CLI flags for connecting to a Kubernetes API server.
|
||||
func BindClientConfigFlags(flags FlagSet, config *Config) {
|
||||
flags.StringVar(&config.Host, "master", config.Host, "The address of the Kubernetes API server")
|
||||
flags.StringVar(&config.Version, "api_version", config.Version, "The API version to use when talking to the server")
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
)
|
||||
|
||||
// Config holds the common attributes that can be passed to a Kubernetes client on
|
||||
// initialization.
|
||||
type Config struct {
|
||||
// Host must be a host string, a host:port pair, or a URL to the base of the API.
|
||||
Host string
|
||||
Version string
|
||||
|
||||
// Server requires Basic authentication
|
||||
Username string
|
||||
Password string
|
||||
|
||||
// Server requires Bearer authentication. This client will not attempt to use
|
||||
// refresh tokens for an OAuth2 flow.
|
||||
// TODO: demonstrate an OAuth2 compatible client.
|
||||
BearerToken string
|
||||
|
||||
// Server requires TLS client certificate authentication
|
||||
CertFile string
|
||||
KeyFile string
|
||||
CAFile string
|
||||
|
||||
// Server should be accessed without verifying the TLS
|
||||
// certificate. For testing only.
|
||||
Insecure bool
|
||||
|
||||
// Transport may be used for custom HTTP behavior. This attribute may not
|
||||
// be specified with the TLS client certificate options.
|
||||
Transport http.RoundTripper
|
||||
|
||||
// Context is the context that should be passed down to the server. If nil, the
|
||||
// context will be set to the appropriate default.
|
||||
Context api.Context
|
||||
}
|
||||
|
||||
// New creates a Kubernetes client for the given config. This client works with pods,
|
||||
// replication controllers and services. It allows operations such as list, get, update
|
||||
// and delete on these objects. An error is returned if the provided configuration
|
||||
// is not valid.
|
||||
func New(c *Config) (*Client, error) {
|
||||
client, err := RESTClientFor(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{client}, nil
|
||||
}
|
||||
|
||||
// NewOrDie creates a Kubernetes client and panics if the provided API version is not recognized.
|
||||
func NewOrDie(c *Config) *Client {
|
||||
client, err := New(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
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
|
||||
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, ", "))
|
||||
}
|
||||
|
||||
baseURL, err := defaultServerUrlFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := NewRESTClient(baseURL, versionInterfaces.Codec)
|
||||
|
||||
transport, err := TransportFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if transport != http.DefaultTransport {
|
||||
client.Client = &http.Client{Transport: transport}
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// TransportFor returns an http.RoundTripper that will provide the authentication
|
||||
// or transport level security defined by the provided Config. Will return the
|
||||
// default http.DefaultTransport if no special case behavior is needed.
|
||||
func TransportFor(config *Config) (http.RoundTripper, error) {
|
||||
// Set transport level security
|
||||
if config.Transport != nil && (config.CertFile != "" || config.Insecure) {
|
||||
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
|
||||
}
|
||||
var transport http.RoundTripper
|
||||
switch {
|
||||
case config.Transport != nil:
|
||||
transport = config.Transport
|
||||
case config.CertFile != "":
|
||||
t, err := NewClientCertTLSTransport(config.CertFile, config.KeyFile, config.CAFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport = t
|
||||
case config.Insecure:
|
||||
transport = NewUnsafeTLSTransport()
|
||||
default:
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
// Set authentication wrappers
|
||||
hasBasicAuth := config.Username != "" || config.Password != ""
|
||||
if hasBasicAuth && config.BearerToken != "" {
|
||||
return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
|
||||
}
|
||||
switch {
|
||||
case config.BearerToken != "":
|
||||
transport = NewBearerAuthRoundTripper(config.BearerToken, transport)
|
||||
case hasBasicAuth:
|
||||
transport = NewBasicAuthRoundTripper(config.Username, config.Password, transport)
|
||||
}
|
||||
|
||||
// TODO: use the config context to wrap a transport
|
||||
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
|
||||
// to use with a Client at a given API version following the standard conventions for a
|
||||
// Kubernetes API.
|
||||
func DefaultServerURL(host, version string, defaultSecure bool) (*url.URL, error) {
|
||||
if host == "" {
|
||||
return nil, fmt.Errorf("host must be a URL or a host:port pair")
|
||||
}
|
||||
if version == "" {
|
||||
return nil, fmt.Errorf("version must be set")
|
||||
}
|
||||
base := host
|
||||
hostURL, err := url.Parse(base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hostURL.Scheme == "" {
|
||||
scheme := "http://"
|
||||
if defaultSecure {
|
||||
scheme = "https://"
|
||||
}
|
||||
hostURL, err = url.Parse(scheme + base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hostURL.Path != "" && hostURL.Path != "/" {
|
||||
return nil, fmt.Errorf("host must be a URL or a host:port pair: %s", base)
|
||||
}
|
||||
}
|
||||
|
||||
// If the user specified a URL without a path component (http://server.com), automatically
|
||||
// append the default API prefix
|
||||
if hostURL.Path == "" {
|
||||
hostURL.Path = "/api"
|
||||
}
|
||||
|
||||
// Add the version to the end of the path
|
||||
hostURL.Path = path.Join(hostURL.Path, version)
|
||||
|
||||
return hostURL, nil
|
||||
}
|
||||
|
||||
// IsConfigTransportSecure returns true iff the provided config will result in a protected
|
||||
// connection to the server when it is passed to client.New() or client.RESTClientFor().
|
||||
// Use to determine when to send credentials over the wire.
|
||||
//
|
||||
// Note: the Insecure flag is ignored when testing for this value, so MITM attacks are
|
||||
// still possible.
|
||||
func IsConfigTransportSecure(config *Config) bool {
|
||||
baseURL, err := defaultServerUrlFor(config)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return baseURL.Scheme == "https"
|
||||
}
|
||||
|
||||
// defaultServerUrlFor is shared between IsConfigSecure and RESTClientFor
|
||||
func defaultServerUrlFor(config *Config) (*url.URL, error) {
|
||||
version := defaultVersionFor(config)
|
||||
// TODO: move the default to secure when the apiserver supports TLS by default
|
||||
defaultSecure := config.CertFile != ""
|
||||
host := config.Host
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
return DefaultServerURL(host, version, defaultSecure)
|
||||
}
|
||||
|
||||
// defaultVersionFor is shared between defaultServerUrlFor and RESTClientFor
|
||||
func defaultVersionFor(config *Config) string {
|
||||
version := config.Version
|
||||
if version == "" {
|
||||
// Clients default to the preferred code API version
|
||||
// TODO: implement version negotiation (highest version supported by server)
|
||||
version = latest.Version
|
||||
}
|
||||
return version
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransportFor(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
Config *Config
|
||||
Err bool
|
||||
Default bool
|
||||
}{
|
||||
"default transport": {
|
||||
Config: &Config{},
|
||||
},
|
||||
}
|
||||
for k, testCase := range testCases {
|
||||
transport, err := TransportFor(testCase.Config)
|
||||
switch {
|
||||
case testCase.Err && err == nil:
|
||||
t.Errorf("%s: unexpected non-error", k)
|
||||
continue
|
||||
case !testCase.Err && err != nil:
|
||||
t.Errorf("%s: unexpected error: %v", k, err)
|
||||
continue
|
||||
}
|
||||
if testCase.Default && transport != http.DefaultTransport {
|
||||
t.Errorf("%s: expected the default transport, got %#v", k, transport)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsConfigTransportSecure(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Config *Config
|
||||
Secure bool
|
||||
}{
|
||||
{
|
||||
Config: &Config{},
|
||||
Secure: false,
|
||||
},
|
||||
{
|
||||
Config: &Config{
|
||||
Host: "https://localhost",
|
||||
},
|
||||
Secure: true,
|
||||
},
|
||||
{
|
||||
Config: &Config{
|
||||
Host: "localhost",
|
||||
CertFile: "foo",
|
||||
},
|
||||
Secure: true,
|
||||
},
|
||||
{
|
||||
Config: &Config{
|
||||
Host: "///:://localhost",
|
||||
CertFile: "foo",
|
||||
},
|
||||
Secure: false,
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
secure := IsConfigTransportSecure(testCase.Config)
|
||||
if testCase.Secure != secure {
|
||||
t.Errorf("expected %d for %#v", testCase.Secure, testCase.Config)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,56 +40,6 @@ import (
|
|||
// are therefore not allowed to set manually.
|
||||
var specialParams = util.NewStringSet("sync", "timeout")
|
||||
|
||||
// Verb begins a request with a verb (GET, POST, PUT, DELETE).
|
||||
//
|
||||
// Example usage of Client's request building interface:
|
||||
// auth, err := LoadAuth(filename)
|
||||
// c := New(url, auth)
|
||||
// resp, err := c.Verb("GET").
|
||||
// Path("pods").
|
||||
// SelectorParam("labels", "area=staging").
|
||||
// Timeout(10*time.Second).
|
||||
// Do()
|
||||
// if err != nil { ... }
|
||||
// list, ok := resp.(*api.PodList)
|
||||
//
|
||||
func (c *RESTClient) Verb(verb string) *Request {
|
||||
return &Request{
|
||||
verb: verb,
|
||||
c: c,
|
||||
path: c.prefix,
|
||||
sync: c.Sync,
|
||||
timeout: c.Timeout,
|
||||
params: map[string]string{},
|
||||
pollPeriod: c.PollPeriod,
|
||||
}
|
||||
}
|
||||
|
||||
// Post begins a POST request. Short for c.Verb("POST").
|
||||
func (c *RESTClient) Post() *Request {
|
||||
return c.Verb("POST")
|
||||
}
|
||||
|
||||
// Put begins a PUT request. Short for c.Verb("PUT").
|
||||
func (c *RESTClient) Put() *Request {
|
||||
return c.Verb("PUT")
|
||||
}
|
||||
|
||||
// Get begins a GET request. Short for c.Verb("GET").
|
||||
func (c *RESTClient) Get() *Request {
|
||||
return c.Verb("GET")
|
||||
}
|
||||
|
||||
// Delete begins a DELETE request. Short for c.Verb("DELETE").
|
||||
func (c *RESTClient) Delete() *Request {
|
||||
return c.Verb("DELETE")
|
||||
}
|
||||
|
||||
// PollFor makes a request to do a single poll of the completion of the given operation.
|
||||
func (c *RESTClient) PollFor(operationID string) *Request {
|
||||
return c.Get().Path("operations").Path(operationID).Sync(false).PollPeriod(0)
|
||||
}
|
||||
|
||||
// Request allows for building up a request to a server in a chained fashion.
|
||||
// Any errors are stored until the end of your call, so you only have to
|
||||
// check once.
|
||||
|
@ -232,7 +182,8 @@ func (r *Request) PollPeriod(d time.Duration) *Request {
|
|||
}
|
||||
|
||||
func (r *Request) finalURL() string {
|
||||
finalURL := r.c.host + r.path
|
||||
finalURL := *r.c.baseURL
|
||||
finalURL.Path = r.path
|
||||
query := url.Values{}
|
||||
for key, value := range r.params {
|
||||
query.Add(key, value)
|
||||
|
@ -245,8 +196,8 @@ func (r *Request) finalURL() string {
|
|||
query.Add("timeout", r.timeout.String())
|
||||
}
|
||||
}
|
||||
finalURL += "?" + query.Encode()
|
||||
return finalURL
|
||||
finalURL.RawQuery = query.Encode()
|
||||
return finalURL.String()
|
||||
}
|
||||
|
||||
// Watch attempts to begin watching the requested location.
|
||||
|
@ -259,10 +210,11 @@ func (r *Request) Watch() (watch.Interface, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.c.auth != nil {
|
||||
req.SetBasicAuth(r.c.auth.User, r.c.auth.Password)
|
||||
client := r.c.Client
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
response, err := r.c.httpClient.Do(req)
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -284,10 +236,11 @@ func (r *Request) Do() Result {
|
|||
}
|
||||
respBody, err := r.c.doRequest(req)
|
||||
if err != nil {
|
||||
if statusErr, ok := err.(*StatusErr); ok {
|
||||
if statusErr.Status.Status == api.StatusWorking && r.pollPeriod != 0 {
|
||||
if statusErr.Status.Details != nil {
|
||||
id := statusErr.Status.Details.ID
|
||||
if s, ok := err.(APIStatus); ok {
|
||||
status := s.Status()
|
||||
if status.Status == api.StatusWorking && r.pollPeriod != 0 {
|
||||
if status.Details != nil {
|
||||
id := status.Details.ID
|
||||
if len(id) > 0 {
|
||||
glog.Infof("Waiting for completion of /operations/%s", id)
|
||||
time.Sleep(r.pollPeriod)
|
||||
|
|
|
@ -48,9 +48,7 @@ func TestDoRequestNewWay(t *testing.T) {
|
|||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
auth := AuthInfo{User: "user", Password: "pass"}
|
||||
ctx := api.NewContext()
|
||||
c := NewOrDie(ctx, testServer.URL, "v1beta2", &auth)
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta2", Username: "user", Password: "pass"})
|
||||
obj, err := c.Verb("POST").
|
||||
Path("foo/bar").
|
||||
Path("baz").
|
||||
|
@ -84,9 +82,7 @@ func TestDoRequestNewWayReader(t *testing.T) {
|
|||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
auth := AuthInfo{User: "user", Password: "pass"}
|
||||
ctx := api.NewContext()
|
||||
c := NewOrDie(ctx, testServer.URL, "v1beta1", &auth)
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
|
||||
obj, err := c.Verb("POST").
|
||||
Path("foo/bar").
|
||||
Path("baz").
|
||||
|
@ -122,9 +118,7 @@ func TestDoRequestNewWayObj(t *testing.T) {
|
|||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
auth := AuthInfo{User: "user", Password: "pass"}
|
||||
ctx := api.NewContext()
|
||||
c := NewOrDie(ctx, testServer.URL, "v1beta2", &auth)
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta2", Username: "user", Password: "pass"})
|
||||
obj, err := c.Verb("POST").
|
||||
Path("foo/bar").
|
||||
Path("baz").
|
||||
|
@ -173,9 +167,7 @@ func TestDoRequestNewWayFile(t *testing.T) {
|
|||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
auth := AuthInfo{User: "user", Password: "pass"}
|
||||
ctx := api.NewContext()
|
||||
c := NewOrDie(ctx, testServer.URL, "v1beta1", &auth)
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
|
||||
obj, err := c.Verb("POST").
|
||||
Path("foo/bar").
|
||||
Path("baz").
|
||||
|
@ -200,8 +192,7 @@ func TestDoRequestNewWayFile(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestVerbs(t *testing.T) {
|
||||
ctx := api.NewContext()
|
||||
c := NewOrDie(ctx, "localhost", "", nil)
|
||||
c := NewOrDie(&Config{})
|
||||
if r := c.Post(); r.verb != "POST" {
|
||||
t.Errorf("Post verb is wrong")
|
||||
}
|
||||
|
@ -217,9 +208,8 @@ func TestVerbs(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAbsPath(t *testing.T) {
|
||||
ctx := api.NewContext()
|
||||
expectedPath := "/bar/foo"
|
||||
c := NewOrDie(ctx, "localhost", "", nil)
|
||||
c := NewOrDie(&Config{})
|
||||
r := c.Post().Path("/foo").AbsPath(expectedPath)
|
||||
if r.path != expectedPath {
|
||||
t.Errorf("unexpected path: %s, expected %s", r.path, expectedPath)
|
||||
|
@ -227,8 +217,7 @@ func TestAbsPath(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSync(t *testing.T) {
|
||||
ctx := api.NewContext()
|
||||
c := NewOrDie(ctx, "localhost", "", nil)
|
||||
c := NewOrDie(&Config{})
|
||||
r := c.Get()
|
||||
if r.sync {
|
||||
t.Errorf("sync has wrong default")
|
||||
|
@ -254,9 +243,8 @@ func TestUintParam(t *testing.T) {
|
|||
{"baz", 0, "http://localhost?baz=0"},
|
||||
}
|
||||
|
||||
ctx := api.NewContext()
|
||||
for _, item := range table {
|
||||
c := NewOrDie(ctx, "localhost", "", nil)
|
||||
c := NewOrDie(&Config{})
|
||||
r := c.Get().AbsPath("").UintParam(item.name, item.testVal)
|
||||
if e, a := item.expectStr, r.finalURL(); e != a {
|
||||
t.Errorf("expected %v, got %v", e, a)
|
||||
|
@ -273,9 +261,9 @@ func TestUnacceptableParamNames(t *testing.T) {
|
|||
{"sync", "foo", false},
|
||||
{"timeout", "42", false},
|
||||
}
|
||||
ctx := api.NewContext()
|
||||
|
||||
for _, item := range table {
|
||||
c := NewOrDie(ctx, "localhost", "", nil)
|
||||
c := NewOrDie(&Config{})
|
||||
r := c.Get().setParam(item.name, item.testVal)
|
||||
if e, a := item.expectSuccess, r.err == nil; e != a {
|
||||
t.Errorf("expected %v, got %v (%v)", e, a, r.err)
|
||||
|
@ -284,8 +272,7 @@ func TestUnacceptableParamNames(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSetPollPeriod(t *testing.T) {
|
||||
ctx := api.NewContext()
|
||||
c := NewOrDie(ctx, "localhost", "", nil)
|
||||
c := NewOrDie(&Config{})
|
||||
r := c.Get()
|
||||
if r.pollPeriod == 0 {
|
||||
t.Errorf("polling should be on by default")
|
||||
|
@ -315,9 +302,7 @@ func TestPolling(t *testing.T) {
|
|||
w.Write(data)
|
||||
}))
|
||||
|
||||
auth := AuthInfo{User: "user", Password: "pass"}
|
||||
ctx := api.NewContext()
|
||||
c := NewOrDie(ctx, testServer.URL, "v1beta1", &auth)
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
|
||||
|
||||
trials := []func(){
|
||||
func() {
|
||||
|
@ -342,7 +327,7 @@ func TestPolling(t *testing.T) {
|
|||
t.Errorf("Unexpected non error: %v", obj)
|
||||
return
|
||||
}
|
||||
if se, ok := err.(*StatusErr); !ok || se.Status.Status != api.StatusWorking {
|
||||
if se, ok := err.(APIStatus); !ok || se.Status().Status != api.StatusWorking {
|
||||
t.Errorf("Unexpected kind of error: %#v", err)
|
||||
return
|
||||
}
|
||||
|
@ -357,7 +342,7 @@ func TestPolling(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func authFromReq(r *http.Request) (*AuthInfo, bool) {
|
||||
func authFromReq(r *http.Request) (*Config, bool) {
|
||||
auth, ok := r.Header["Authorization"]
|
||||
if !ok {
|
||||
return nil, false
|
||||
|
@ -376,16 +361,16 @@ func authFromReq(r *http.Request) (*AuthInfo, bool) {
|
|||
if len(parts) != 2 {
|
||||
return nil, false
|
||||
}
|
||||
return &AuthInfo{User: parts[0], Password: parts[1]}, true
|
||||
return &Config{Username: parts[0], Password: parts[1]}, true
|
||||
}
|
||||
|
||||
// checkAuth sets errors if the auth found in r doesn't match the expectation.
|
||||
// TODO: Move to util, test in more places.
|
||||
func checkAuth(t *testing.T, expect AuthInfo, r *http.Request) {
|
||||
func checkAuth(t *testing.T, expect *Config, r *http.Request) {
|
||||
foundAuth, found := authFromReq(r)
|
||||
if !found {
|
||||
t.Errorf("no auth found")
|
||||
} else if e, a := expect, *foundAuth; !reflect.DeepEqual(e, a) {
|
||||
} else if e, a := expect, foundAuth; !reflect.DeepEqual(e, a) {
|
||||
t.Fatalf("Wrong basic auth: wanted %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
@ -400,7 +385,7 @@ func TestWatch(t *testing.T) {
|
|||
{watch.Deleted, &api.Pod{JSONBase: api.JSONBase{ID: "last"}}},
|
||||
}
|
||||
|
||||
auth := AuthInfo{User: "user", Password: "pass"}
|
||||
auth := &Config{Username: "user", Password: "pass"}
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
checkAuth(t, auth, r)
|
||||
flusher, ok := w.(http.Flusher)
|
||||
|
@ -421,8 +406,12 @@ func TestWatch(t *testing.T) {
|
|||
}
|
||||
}))
|
||||
|
||||
ctx := api.NewContext()
|
||||
s, err := New(ctx, testServer.URL, "v1beta1", &auth)
|
||||
s, err := New(&Config{
|
||||
Host: testServer.URL,
|
||||
Version: "v1beta1",
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// RESTClient imposes common Kubernetes API conventions on a set of resource paths.
|
||||
// The baseURL is expected to point to an HTTP or HTTPS path that is the parent
|
||||
// of one or more resources. The server should return a decodable API resource
|
||||
// object, or an api.Status object which contains information about the reason for
|
||||
// any failure.
|
||||
//
|
||||
// Most consumers should use client.New() to get a Kubernetes API client.
|
||||
type RESTClient struct {
|
||||
baseURL *url.URL
|
||||
|
||||
// Codec is the encoding and decoding scheme that applies to a particular set of
|
||||
// REST resources.
|
||||
Codec runtime.Codec
|
||||
|
||||
// Set specific behavior of the client. If not set http.DefaultClient will be
|
||||
// used.
|
||||
Client *http.Client
|
||||
|
||||
Sync bool
|
||||
PollPeriod time.Duration
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewRESTClient(baseURL *url.URL, c runtime.Codec) *RESTClient {
|
||||
base := *baseURL
|
||||
if !strings.HasSuffix(base.Path, "/") {
|
||||
base.Path += "/"
|
||||
}
|
||||
base.RawQuery = ""
|
||||
base.Fragment = ""
|
||||
|
||||
return &RESTClient{
|
||||
baseURL: &base,
|
||||
Codec: c,
|
||||
|
||||
// Make asynchronous requests by default
|
||||
// TODO: flip me to the default
|
||||
Sync: false,
|
||||
// Poll frequently when asynchronous requests are provided
|
||||
PollPeriod: time.Second * 2,
|
||||
}
|
||||
}
|
||||
|
||||
// doRequest executes a request against a server
|
||||
func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
|
||||
client := c.Client
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return body, err
|
||||
}
|
||||
|
||||
// Did the server give us a status response?
|
||||
isStatusResponse := false
|
||||
var status api.Status
|
||||
if err := c.Codec.DecodeInto(body, &status); err == nil && status.Status != "" {
|
||||
isStatusResponse = true
|
||||
}
|
||||
|
||||
switch {
|
||||
case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent:
|
||||
if !isStatusResponse {
|
||||
return nil, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
|
||||
}
|
||||
return nil, errors.FromObject(&status)
|
||||
}
|
||||
|
||||
// If the server gave us a status back, look at what it was.
|
||||
if isStatusResponse && status.Status != api.StatusSuccess {
|
||||
// "Working" requests need to be handled specially.
|
||||
// "Failed" requests are clearly just an error and it makes sense to return them as such.
|
||||
return nil, errors.FromObject(&status)
|
||||
}
|
||||
|
||||
return body, err
|
||||
}
|
||||
|
||||
// Verb begins a request with a verb (GET, POST, PUT, DELETE).
|
||||
//
|
||||
// Example usage of RESTClient's request building interface:
|
||||
// c := NewRESTClient(url, codec)
|
||||
// resp, err := c.Verb("GET").
|
||||
// Path("pods").
|
||||
// SelectorParam("labels", "area=staging").
|
||||
// Timeout(10*time.Second).
|
||||
// Do()
|
||||
// if err != nil { ... }
|
||||
// list, ok := resp.(*api.PodList)
|
||||
//
|
||||
func (c *RESTClient) Verb(verb string) *Request {
|
||||
// TODO: uncomment when Go 1.2 support is dropped
|
||||
//var timeout time.Duration = 0
|
||||
// if c.Client != nil {
|
||||
// timeout = c.Client.Timeout
|
||||
// }
|
||||
return &Request{
|
||||
verb: verb,
|
||||
c: c,
|
||||
path: c.baseURL.Path,
|
||||
sync: c.Sync,
|
||||
timeout: c.Timeout,
|
||||
params: map[string]string{},
|
||||
pollPeriod: c.PollPeriod,
|
||||
}
|
||||
}
|
||||
|
||||
// Post begins a POST request. Short for c.Verb("POST").
|
||||
func (c *RESTClient) Post() *Request {
|
||||
return c.Verb("POST")
|
||||
}
|
||||
|
||||
// Put begins a PUT request. Short for c.Verb("PUT").
|
||||
func (c *RESTClient) Put() *Request {
|
||||
return c.Verb("PUT")
|
||||
}
|
||||
|
||||
// Get begins a GET request. Short for c.Verb("GET").
|
||||
func (c *RESTClient) Get() *Request {
|
||||
return c.Verb("GET")
|
||||
}
|
||||
|
||||
// Delete begins a DELETE request. Short for c.Verb("DELETE").
|
||||
func (c *RESTClient) Delete() *Request {
|
||||
return c.Verb("DELETE")
|
||||
}
|
||||
|
||||
// PollFor makes a request to do a single poll of the completion of the given operation.
|
||||
func (c *RESTClient) PollFor(operationID string) *Request {
|
||||
return c.Get().Path("operations").Path(operationID).Sync(false).PollPeriod(0)
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
func TestChecksCodec(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
Err bool
|
||||
Prefix string
|
||||
Codec runtime.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})
|
||||
switch {
|
||||
case err == nil && expected.Err:
|
||||
t.Errorf("expected error but was nil")
|
||||
continue
|
||||
case err != nil && !expected.Err:
|
||||
t.Errorf("unexpected error %v", err)
|
||||
continue
|
||||
case err != nil:
|
||||
continue
|
||||
}
|
||||
if e, a := expected.Prefix, client.baseURL.Path; e != a {
|
||||
t.Errorf("expected %#v, got %#v", e, a)
|
||||
}
|
||||
if e, a := expected.Codec, client.Codec; e != a {
|
||||
t.Errorf("expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatesHostParameter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
URL string
|
||||
Err bool
|
||||
}{
|
||||
"127.0.0.1": {"http://127.0.0.1/api/v1beta1/", false},
|
||||
"127.0.0.1:8080": {"http://127.0.0.1:8080/api/v1beta1/", false},
|
||||
"foo.bar.com": {"http://foo.bar.com/api/v1beta1/", false},
|
||||
"http://host/prefix": {"http://host/prefix/v1beta1/", false},
|
||||
"http://host": {"http://host/api/v1beta1/", false},
|
||||
"host/server": {"", true},
|
||||
}
|
||||
for k, expected := range testCases {
|
||||
c, err := RESTClientFor(&Config{Host: k, Version: "v1beta1"})
|
||||
switch {
|
||||
case err == nil && expected.Err:
|
||||
t.Errorf("expected error but was nil")
|
||||
continue
|
||||
case err != nil && !expected.Err:
|
||||
t.Errorf("unexpected error %v", err)
|
||||
continue
|
||||
case err != nil:
|
||||
continue
|
||||
}
|
||||
if e, a := expected.URL, c.baseURL.String(); e != a {
|
||||
t.Errorf("%s: expected host %s, got %s", k, e, a)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRequest(t *testing.T) {
|
||||
invalid := "aaaaa"
|
||||
uri, _ := url.Parse("http://localhost")
|
||||
testClients := []testClient{
|
||||
{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 200}},
|
||||
{Request: testRequest{Method: "GET", Path: "bad%ZZ"}, Error: true},
|
||||
{Client: &Client{&RESTClient{baseURL: uri}}, Request: testRequest{Method: "GET", Path: "nocertificate"}, Error: true},
|
||||
{Request: testRequest{Method: "GET", Path: "error"}, Response: Response{StatusCode: 500}, Error: true},
|
||||
{Request: testRequest{Method: "POST", Path: "faildecode"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
|
||||
{Request: testRequest{Method: "GET", Path: "failread"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
|
||||
}
|
||||
for _, c := range testClients {
|
||||
client := c.Setup()
|
||||
prefix := *client.baseURL
|
||||
prefix.Path += c.Request.Path
|
||||
request := &http.Request{
|
||||
Method: c.Request.Method,
|
||||
Header: make(http.Header),
|
||||
URL: &prefix,
|
||||
}
|
||||
response, err := client.doRequest(request)
|
||||
//t.Logf("dorequest: %#v\n%#v\n%v", request.URL, response, err)
|
||||
c.ValidateRaw(t, response, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRequestBearer(t *testing.T) {
|
||||
status := &api.Status{Status: api.StatusWorking}
|
||||
expectedBody, _ := latest.Codec.Encode(status)
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 202,
|
||||
ResponseBody: string(expectedBody),
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, BearerToken: "test"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
c.doRequest(request)
|
||||
if fakeHandler.RequestReceived.Header.Get("Authorization") != "Bearer test" {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRequestAccepted(t *testing.T) {
|
||||
status := &api.Status{Status: api.StatusWorking}
|
||||
expectedBody, _ := latest.Codec.Encode(status)
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 202,
|
||||
ResponseBody: string(expectedBody),
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "test"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, err := c.doRequest(request)
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("Unexpected non-error")
|
||||
return
|
||||
}
|
||||
se, ok := err.(APIStatus)
|
||||
if !ok {
|
||||
t.Errorf("Unexpected kind of error: %#v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(se.Status(), *status) {
|
||||
t.Errorf("Unexpected status: %#v %#v", se.Status(), status)
|
||||
}
|
||||
if body != nil {
|
||||
t.Errorf("Expected nil body, but saw: '%s'", body)
|
||||
}
|
||||
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
|
||||
}
|
||||
|
||||
func TestDoRequestAcceptedSuccess(t *testing.T) {
|
||||
status := &api.Status{Status: api.StatusSuccess}
|
||||
expectedBody, _ := latest.Codec.Encode(status)
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 202,
|
||||
ResponseBody: string(expectedBody),
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, err := c.doRequest(request)
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
statusOut, err := latest.Codec.Decode(body)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(status, statusOut) {
|
||||
t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, statusOut)
|
||||
}
|
||||
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
|
||||
}
|
||||
|
||||
func TestDoRequestFailed(t *testing.T) {
|
||||
status := &api.Status{Status: api.StatusFailure, Reason: api.StatusReasonInvalid, Details: &api.StatusDetails{ID: "test", Kind: "test"}}
|
||||
expectedBody, _ := latest.Codec.Encode(status)
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 404,
|
||||
ResponseBody: string(expectedBody),
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, err := c.doRequest(request)
|
||||
if err == nil || body != nil {
|
||||
t.Errorf("unexpected non-error: %#v", body)
|
||||
}
|
||||
ss, ok := err.(APIStatus)
|
||||
if !ok {
|
||||
t.Errorf("unexpected error type %v", err)
|
||||
}
|
||||
actual := ss.Status()
|
||||
if !reflect.DeepEqual(status, &actual) {
|
||||
t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, actual)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type basicAuthRoundTripper struct {
|
||||
username string
|
||||
password string
|
||||
rt http.RoundTripper
|
||||
}
|
||||
|
||||
func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
|
||||
return &basicAuthRoundTripper{username, password, rt}
|
||||
}
|
||||
|
||||
func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req = cloneRequest(req)
|
||||
req.SetBasicAuth(rt.username, rt.password)
|
||||
return rt.rt.RoundTrip(req)
|
||||
}
|
||||
|
||||
type bearerAuthRoundTripper struct {
|
||||
bearer string
|
||||
rt http.RoundTripper
|
||||
}
|
||||
|
||||
func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
|
||||
return &bearerAuthRoundTripper{bearer, rt}
|
||||
}
|
||||
|
||||
func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req = cloneRequest(req)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rt.bearer))
|
||||
return rt.rt.RoundTrip(req)
|
||||
}
|
||||
|
||||
func NewClientCertTLSTransport(certFile, keyFile, caFile string) (*http.Transport, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := ioutil.ReadFile(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AppendCertsFromPEM(data)
|
||||
return &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{
|
||||
cert,
|
||||
},
|
||||
RootCAs: certPool,
|
||||
ClientCAs: certPool,
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewUnsafeTLSTransport() *http.Transport {
|
||||
return &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request.
|
||||
// The clone is a shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
// shallow copy of the struct
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
// deep copy of the Header
|
||||
r2.Header = make(http.Header)
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = s
|
||||
}
|
||||
return r2
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnsecuredTLSTransport(t *testing.T) {
|
||||
transport := NewUnsafeTLSTransport()
|
||||
if !transport.TLSClientConfig.InsecureSkipVerify {
|
||||
t.Errorf("expected transport to be insecure")
|
||||
}
|
||||
}
|
||||
|
||||
type testRoundTripper struct {
|
||||
Request *http.Request
|
||||
Response *http.Response
|
||||
Err error
|
||||
}
|
||||
|
||||
func (rt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
rt.Request = req
|
||||
return rt.Response, rt.Err
|
||||
}
|
||||
|
||||
func TestBearerAuthRoundTripper(t *testing.T) {
|
||||
rt := &testRoundTripper{}
|
||||
req := &http.Request{}
|
||||
NewBearerAuthRoundTripper("test", rt).RoundTrip(req)
|
||||
if rt.Request == nil {
|
||||
t.Fatalf("unexpected nil request", rt)
|
||||
}
|
||||
if rt.Request == req {
|
||||
t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
|
||||
}
|
||||
if rt.Request.Header.Get("Authorization") != "Bearer test" {
|
||||
t.Errorf("unexpected authorization header: %#v", rt.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuthRoundTripper(t *testing.T) {
|
||||
rt := &testRoundTripper{}
|
||||
req := &http.Request{}
|
||||
NewBasicAuthRoundTripper("user", "pass", rt).RoundTrip(req)
|
||||
if rt.Request == nil {
|
||||
t.Fatalf("unexpected nil request", rt)
|
||||
}
|
||||
if rt.Request == req {
|
||||
t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
|
||||
}
|
||||
if rt.Request.Header.Get("Authorization") != "Basic "+base64.StdEncoding.EncodeToString([]byte("user:pass")) {
|
||||
t.Errorf("unexpected authorization header: %#v", rt.Request)
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
@ -28,7 +29,7 @@ import (
|
|||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
|
@ -38,11 +39,8 @@ import (
|
|||
"github.com/coreos/go-etcd/etcd"
|
||||
)
|
||||
|
||||
// TODO: Move this to a common place, it's needed in multiple tests.
|
||||
var apiPath = "/api/v1beta1"
|
||||
|
||||
func makeURL(suffix string) string {
|
||||
return apiPath + suffix
|
||||
return path.Join("/api", testapi.Version(), suffix)
|
||||
}
|
||||
|
||||
type FakePodControl struct {
|
||||
|
@ -116,8 +114,8 @@ func TestSyncReplicationControllerDoesNothing(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
ResponseBody: string(body),
|
||||
}
|
||||
testServer := httptest.NewTLSServer(&fakeHandler)
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
|
||||
fakePodControl := FakePodControl{}
|
||||
|
||||
|
@ -136,8 +134,8 @@ func TestSyncReplicationControllerDeletes(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
ResponseBody: string(body),
|
||||
}
|
||||
testServer := httptest.NewTLSServer(&fakeHandler)
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
|
||||
fakePodControl := FakePodControl{}
|
||||
|
||||
|
@ -151,13 +149,13 @@ func TestSyncReplicationControllerDeletes(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSyncReplicationControllerCreates(t *testing.T) {
|
||||
body, _ := latest.Codec.Encode(newPodList(0))
|
||||
body := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), newPodList(0))
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: string(body),
|
||||
}
|
||||
testServer := httptest.NewTLSServer(&fakeHandler)
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
|
||||
fakePodControl := FakePodControl{}
|
||||
|
||||
|
@ -171,13 +169,13 @@ func TestSyncReplicationControllerCreates(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCreateReplica(t *testing.T) {
|
||||
body, _ := v1beta1.Codec.Encode(&api.Pod{})
|
||||
body := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), &api.Pod{})
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: string(body),
|
||||
}
|
||||
testServer := httptest.NewTLSServer(&fakeHandler)
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
|
||||
podControl := RealPodControl{
|
||||
kubeClient: client,
|
||||
|
@ -211,7 +209,7 @@ func TestCreateReplica(t *testing.T) {
|
|||
expectedPod := api.Pod{
|
||||
JSONBase: api.JSONBase{
|
||||
Kind: "Pod",
|
||||
APIVersion: latest.Version,
|
||||
APIVersion: testapi.Version(),
|
||||
},
|
||||
Labels: controllerSpec.DesiredState.PodTemplate.Labels,
|
||||
DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState,
|
||||
|
@ -229,7 +227,7 @@ func TestCreateReplica(t *testing.T) {
|
|||
|
||||
func TestSynchonize(t *testing.T) {
|
||||
controllerSpec1 := api.ReplicationController{
|
||||
JSONBase: api.JSONBase{APIVersion: "v1beta1"},
|
||||
JSONBase: api.JSONBase{APIVersion: testapi.Version()},
|
||||
DesiredState: api.ReplicationControllerState{
|
||||
Replicas: 4,
|
||||
PodTemplate: api.PodTemplate{
|
||||
|
@ -250,7 +248,7 @@ func TestSynchonize(t *testing.T) {
|
|||
},
|
||||
}
|
||||
controllerSpec2 := api.ReplicationController{
|
||||
JSONBase: api.JSONBase{APIVersion: "v1beta1"},
|
||||
JSONBase: api.JSONBase{APIVersion: testapi.Version()},
|
||||
DesiredState: api.ReplicationControllerState{
|
||||
Replicas: 3,
|
||||
PodTemplate: api.PodTemplate{
|
||||
|
@ -289,7 +287,7 @@ func TestSynchonize(t *testing.T) {
|
|||
|
||||
fakePodHandler := util.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: "{\"apiVersion\": \"" + latest.Version + "\", \"kind\": \"PodList\"}",
|
||||
ResponseBody: "{\"apiVersion\": \"" + testapi.Version() + "\", \"kind\": \"PodList\"}",
|
||||
T: t,
|
||||
}
|
||||
fakeControllerHandler := util.FakeHandler{
|
||||
|
@ -303,14 +301,14 @@ func TestSynchonize(t *testing.T) {
|
|||
T: t,
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/api/v1beta1/pods/", &fakePodHandler)
|
||||
mux.Handle("/api/v1beta1/replicationControllers/", &fakeControllerHandler)
|
||||
mux.Handle("/api/"+testapi.Version()+"/pods/", &fakePodHandler)
|
||||
mux.Handle("/api/"+testapi.Version()+"/replicationControllers/", &fakeControllerHandler)
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
t.Errorf("Unexpected request for %v", req.RequestURI)
|
||||
})
|
||||
testServer := httptest.NewServer(mux)
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
manager := NewReplicationManager(client)
|
||||
fakePodControl := FakePodControl{}
|
||||
manager.podControl = &fakePodControl
|
||||
|
|
|
@ -51,9 +51,14 @@ func promptForString(field string, r io.Reader) string {
|
|||
return result
|
||||
}
|
||||
|
||||
type AuthInfo struct {
|
||||
User string
|
||||
Password string
|
||||
}
|
||||
|
||||
// LoadAuthInfo parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
|
||||
func LoadAuthInfo(path string, r io.Reader) (*client.AuthInfo, error) {
|
||||
var auth client.AuthInfo
|
||||
func LoadAuthInfo(path string, r io.Reader) (*AuthInfo, error) {
|
||||
var auth AuthInfo
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
auth.User = promptForString("Username", r)
|
||||
auth.Password = promptForString("Password", r)
|
||||
|
|
|
@ -222,12 +222,12 @@ func TestCloudCfgDeleteControllerWithReplicas(t *testing.T) {
|
|||
func TestLoadAuthInfo(t *testing.T) {
|
||||
loadAuthInfoTests := []struct {
|
||||
authData string
|
||||
authInfo *client.AuthInfo
|
||||
authInfo *AuthInfo
|
||||
r io.Reader
|
||||
}{
|
||||
{
|
||||
`{"user": "user", "password": "pass"}`,
|
||||
&client.AuthInfo{User: "user", Password: "pass"},
|
||||
&AuthInfo{User: "user", Password: "pass"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
|
@ -235,7 +235,7 @@ func TestLoadAuthInfo(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"missing",
|
||||
&client.AuthInfo{User: "user", Password: "pass"},
|
||||
&AuthInfo{User: "user", Password: "pass"},
|
||||
bytes.NewBufferString("user\npass"),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import (
|
|||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
|
@ -37,7 +37,7 @@ func newPodList(count int) api.PodList {
|
|||
pods = append(pods, api.Pod{
|
||||
JSONBase: api.JSONBase{
|
||||
ID: fmt.Sprintf("pod%d", i),
|
||||
APIVersion: "v1beta1",
|
||||
APIVersion: testapi.Version(),
|
||||
},
|
||||
DesiredState: api.PodState{
|
||||
Manifest: api.ContainerManifest{
|
||||
|
@ -58,7 +58,7 @@ func newPodList(count int) api.PodList {
|
|||
})
|
||||
}
|
||||
return api.PodList{
|
||||
JSONBase: api.JSONBase{APIVersion: "v1beta1", Kind: "PodList"},
|
||||
JSONBase: api.JSONBase{APIVersion: testapi.Version(), Kind: "PodList"},
|
||||
Items: pods,
|
||||
}
|
||||
}
|
||||
|
@ -143,10 +143,10 @@ func makeTestServer(t *testing.T, podResponse serverResponse, serviceResponse se
|
|||
ResponseBody: util.EncodeJSON(endpointsResponse.obj),
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/api/v1beta1/pods", &fakePodHandler)
|
||||
mux.Handle("/api/v1beta1/services", &fakeServiceHandler)
|
||||
mux.Handle("/api/v1beta1/endpoints", &fakeEndpointsHandler)
|
||||
mux.Handle("/api/v1beta1/endpoints/", &fakeEndpointsHandler)
|
||||
mux.Handle("/api/"+testapi.Version()+"/pods", &fakePodHandler)
|
||||
mux.Handle("/api/"+testapi.Version()+"/services", &fakeServiceHandler)
|
||||
mux.Handle("/api/"+testapi.Version()+"/endpoints", &fakeEndpointsHandler)
|
||||
mux.Handle("/api/"+testapi.Version()+"/endpoints/", &fakeEndpointsHandler)
|
||||
mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
|
||||
t.Errorf("unexpected request: %v", req.RequestURI)
|
||||
res.WriteHeader(http.StatusNotFound)
|
||||
|
@ -159,7 +159,7 @@ func TestSyncEndpointsEmpty(t *testing.T) {
|
|||
serverResponse{http.StatusOK, newPodList(0)},
|
||||
serverResponse{http.StatusOK, api.ServiceList{}},
|
||||
serverResponse{http.StatusOK, api.Endpoints{}})
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
serviceRegistry := registrytest.ServiceRegistry{}
|
||||
endpoints := NewEndpointController(&serviceRegistry, client)
|
||||
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
||||
|
@ -172,7 +172,7 @@ func TestSyncEndpointsError(t *testing.T) {
|
|||
serverResponse{http.StatusOK, newPodList(0)},
|
||||
serverResponse{http.StatusInternalServerError, api.ServiceList{}},
|
||||
serverResponse{http.StatusOK, api.Endpoints{}})
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
serviceRegistry := registrytest.ServiceRegistry{
|
||||
Err: fmt.Errorf("test error"),
|
||||
}
|
||||
|
@ -203,20 +203,20 @@ func TestSyncEndpointsItemsPreexisting(t *testing.T) {
|
|||
},
|
||||
Endpoints: []string{"6.7.8.9:1000"},
|
||||
}})
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
serviceRegistry := registrytest.ServiceRegistry{}
|
||||
endpoints := NewEndpointController(&serviceRegistry, client)
|
||||
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
data := runtime.EncodeOrDie(v1beta1.Codec, &api.Endpoints{
|
||||
data := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), &api.Endpoints{
|
||||
JSONBase: api.JSONBase{
|
||||
ID: "foo",
|
||||
ResourceVersion: 1,
|
||||
},
|
||||
Endpoints: []string{"1.2.3.4:8080"},
|
||||
})
|
||||
endpointsHandler.ValidateRequest(t, "/api/v1beta1/endpoints/foo", "PUT", &data)
|
||||
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo", "PUT", &data)
|
||||
}
|
||||
|
||||
func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) {
|
||||
|
@ -239,13 +239,13 @@ func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) {
|
|||
},
|
||||
Endpoints: []string{"1.2.3.4:8080"},
|
||||
}})
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
serviceRegistry := registrytest.ServiceRegistry{}
|
||||
endpoints := NewEndpointController(&serviceRegistry, client)
|
||||
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
endpointsHandler.ValidateRequest(t, "/api/v1beta1/endpoints/foo", "GET", nil)
|
||||
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo", "GET", nil)
|
||||
}
|
||||
|
||||
func TestSyncEndpointsItems(t *testing.T) {
|
||||
|
@ -263,19 +263,19 @@ func TestSyncEndpointsItems(t *testing.T) {
|
|||
serverResponse{http.StatusOK, newPodList(1)},
|
||||
serverResponse{http.StatusOK, serviceList},
|
||||
serverResponse{http.StatusOK, api.Endpoints{}})
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
serviceRegistry := registrytest.ServiceRegistry{}
|
||||
endpoints := NewEndpointController(&serviceRegistry, client)
|
||||
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
data := runtime.EncodeOrDie(v1beta1.Codec, &api.Endpoints{
|
||||
data := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), &api.Endpoints{
|
||||
JSONBase: api.JSONBase{
|
||||
ResourceVersion: 0,
|
||||
},
|
||||
Endpoints: []string{"1.2.3.4:8080"},
|
||||
})
|
||||
endpointsHandler.ValidateRequest(t, "/api/v1beta1/endpoints", "POST", &data)
|
||||
endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints", "POST", &data)
|
||||
}
|
||||
|
||||
func TestSyncEndpointsPodError(t *testing.T) {
|
||||
|
@ -292,7 +292,7 @@ func TestSyncEndpointsPodError(t *testing.T) {
|
|||
serverResponse{http.StatusInternalServerError, api.PodList{}},
|
||||
serverResponse{http.StatusOK, serviceList},
|
||||
serverResponse{http.StatusOK, api.Endpoints{}})
|
||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||
serviceRegistry := registrytest.ServiceRegistry{
|
||||
List: api.ServiceList{
|
||||
Items: []api.Service{
|
||||
|
|
|
@ -65,6 +65,10 @@ func (f FakeHandler) ValidateRequest(t TestInterface, expectedPath, expectedMeth
|
|||
if err != nil {
|
||||
t.Errorf("Couldn't parse %v as a URL.", expectedPath)
|
||||
}
|
||||
if f.RequestReceived == nil {
|
||||
t.Errorf("Unexpected nil request received for %s", expectedPath)
|
||||
return
|
||||
}
|
||||
if f.RequestReceived.URL.Path != expectURL.Path {
|
||||
t.Errorf("Unexpected request path for request %#v, received: %q, expected: %q", f.RequestReceived, f.RequestReceived.URL.Path, expectURL.Path)
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
||||
masterPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||
|
@ -35,11 +33,15 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
master = flag.String("master", "", "The address of the Kubernetes API server")
|
||||
port = flag.Int("port", masterPkg.SchedulerPort, "The port that the scheduler's http service runs on")
|
||||
address = flag.String("address", "127.0.0.1", "The address to serve from")
|
||||
port = flag.Int("port", masterPkg.SchedulerPort, "The port that the scheduler's http service runs on")
|
||||
address = flag.String("address", "127.0.0.1", "The address to serve from")
|
||||
clientConfig = &client.Config{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
client.BindClientConfigFlags(flag.CommandLine, clientConfig)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
util.InitLogs()
|
||||
|
@ -47,11 +49,9 @@ func main() {
|
|||
|
||||
verflag.PrintAndExitIfRequested()
|
||||
|
||||
// TODO: security story for plugins!
|
||||
ctx := api.NewContext()
|
||||
kubeClient, err := client.New(ctx, *master, latest.OldestVersion, nil)
|
||||
kubeClient, err := client.New(clientConfig)
|
||||
if err != nil {
|
||||
glog.Fatalf("Invalid -master: %v", err)
|
||||
glog.Fatalf("Invalid API configuration: %v", err)
|
||||
}
|
||||
|
||||
go http.ListenAndServe(net.JoinHostPort(*address, strconv.Itoa(*port)), nil)
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
|
@ -39,7 +40,7 @@ func TestCreate(t *testing.T) {
|
|||
T: t,
|
||||
}
|
||||
server := httptest.NewServer(&handler)
|
||||
client := client.NewOrDie(api.NewContext(), server.URL, "", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
|
||||
factory := ConfigFactory{client}
|
||||
factory.Create()
|
||||
}
|
||||
|
@ -52,17 +53,17 @@ func TestCreateLists(t *testing.T) {
|
|||
}{
|
||||
// Minion
|
||||
{
|
||||
location: "/api/v1beta1/minions?fields=",
|
||||
location: "/api/" + testapi.Version() + "/minions?fields=",
|
||||
factory: factory.createMinionLW,
|
||||
},
|
||||
// Assigned pod
|
||||
{
|
||||
location: "/api/v1beta1/pods?fields=DesiredState.Host!%3D",
|
||||
location: "/api/" + testapi.Version() + "/pods?fields=DesiredState.Host!%3D",
|
||||
factory: factory.createAssignedPodLW,
|
||||
},
|
||||
// Unassigned pod
|
||||
{
|
||||
location: "/api/v1beta1/pods?fields=DesiredState.Host%3D",
|
||||
location: "/api/" + testapi.Version() + "/pods?fields=DesiredState.Host%3D",
|
||||
factory: factory.createUnassignedPodLW,
|
||||
},
|
||||
}
|
||||
|
@ -74,7 +75,7 @@ func TestCreateLists(t *testing.T) {
|
|||
T: t,
|
||||
}
|
||||
server := httptest.NewServer(&handler)
|
||||
factory.Client = client.NewOrDie(api.NewContext(), server.URL, latest.OldestVersion, nil)
|
||||
factory.Client = client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
|
||||
// This test merely tests that the correct request is made.
|
||||
item.factory().List()
|
||||
handler.ValidateRequest(t, item.location, "GET", nil)
|
||||
|
@ -91,31 +92,31 @@ func TestCreateWatches(t *testing.T) {
|
|||
// Minion watch
|
||||
{
|
||||
rv: 0,
|
||||
location: "/api/v1beta1/watch/minions?fields=&resourceVersion=0",
|
||||
location: "/api/" + testapi.Version() + "/watch/minions?fields=&resourceVersion=0",
|
||||
factory: factory.createMinionLW,
|
||||
}, {
|
||||
rv: 42,
|
||||
location: "/api/v1beta1/watch/minions?fields=&resourceVersion=42",
|
||||
location: "/api/" + testapi.Version() + "/watch/minions?fields=&resourceVersion=42",
|
||||
factory: factory.createMinionLW,
|
||||
},
|
||||
// Assigned pod watches
|
||||
{
|
||||
rv: 0,
|
||||
location: "/api/v1beta1/watch/pods?fields=DesiredState.Host!%3D&resourceVersion=0",
|
||||
location: "/api/" + testapi.Version() + "/watch/pods?fields=DesiredState.Host!%3D&resourceVersion=0",
|
||||
factory: factory.createAssignedPodLW,
|
||||
}, {
|
||||
rv: 42,
|
||||
location: "/api/v1beta1/watch/pods?fields=DesiredState.Host!%3D&resourceVersion=42",
|
||||
location: "/api/" + testapi.Version() + "/watch/pods?fields=DesiredState.Host!%3D&resourceVersion=42",
|
||||
factory: factory.createAssignedPodLW,
|
||||
},
|
||||
// Unassigned pod watches
|
||||
{
|
||||
rv: 0,
|
||||
location: "/api/v1beta1/watch/pods?fields=DesiredState.Host%3D&resourceVersion=0",
|
||||
location: "/api/" + testapi.Version() + "/watch/pods?fields=DesiredState.Host%3D&resourceVersion=0",
|
||||
factory: factory.createUnassignedPodLW,
|
||||
}, {
|
||||
rv: 42,
|
||||
location: "/api/v1beta1/watch/pods?fields=DesiredState.Host%3D&resourceVersion=42",
|
||||
location: "/api/" + testapi.Version() + "/watch/pods?fields=DesiredState.Host%3D&resourceVersion=42",
|
||||
factory: factory.createUnassignedPodLW,
|
||||
},
|
||||
}
|
||||
|
@ -127,7 +128,7 @@ func TestCreateWatches(t *testing.T) {
|
|||
T: t,
|
||||
}
|
||||
server := httptest.NewServer(&handler)
|
||||
factory.Client = client.NewOrDie(api.NewContext(), server.URL, "v1beta1", nil)
|
||||
factory.Client = client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
|
||||
// This test merely tests that the correct request is made.
|
||||
item.factory().Watch(item.rv)
|
||||
handler.ValidateRequest(t, item.location, "GET", nil)
|
||||
|
@ -155,9 +156,9 @@ func TestPollMinions(t *testing.T) {
|
|||
}
|
||||
mux := http.NewServeMux()
|
||||
// FakeHandler musn't be sent requests other than the one you want to test.
|
||||
mux.Handle("/api/v1beta1/minions", &handler)
|
||||
mux.Handle("/api/"+testapi.Version()+"/minions", &handler)
|
||||
server := httptest.NewServer(mux)
|
||||
client := client.NewOrDie(api.NewContext(), server.URL, "v1beta1", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
|
||||
cf := ConfigFactory{client}
|
||||
|
||||
ce, err := cf.pollMinions()
|
||||
|
@ -165,7 +166,7 @@ func TestPollMinions(t *testing.T) {
|
|||
t.Errorf("Unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
handler.ValidateRequest(t, "/api/v1beta1/minions", "GET", nil)
|
||||
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/minions", "GET", nil)
|
||||
|
||||
if e, a := len(item.minions), ce.Len(); e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
|
@ -182,9 +183,9 @@ func TestDefaultErrorFunc(t *testing.T) {
|
|||
}
|
||||
mux := http.NewServeMux()
|
||||
// FakeHandler musn't be sent requests other than the one you want to test.
|
||||
mux.Handle("/api/v1beta1/pods/foo", &handler)
|
||||
mux.Handle("/api/"+testapi.Version()+"/pods/foo", &handler)
|
||||
server := httptest.NewServer(mux)
|
||||
factory := ConfigFactory{client.NewOrDie(api.NewContext(), server.URL, "", nil)}
|
||||
factory := ConfigFactory{client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})}
|
||||
queue := cache.NewFIFO()
|
||||
errFunc := factory.makeDefaultErrorFunc(queue)
|
||||
|
||||
|
@ -198,7 +199,7 @@ func TestDefaultErrorFunc(t *testing.T) {
|
|||
if !exists {
|
||||
continue
|
||||
}
|
||||
handler.ValidateRequest(t, "/api/v1beta1/pods/foo", "GET", nil)
|
||||
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/pods/foo", "GET", nil)
|
||||
if e, a := testPod, got; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
|
@ -289,14 +290,14 @@ func TestBind(t *testing.T) {
|
|||
T: t,
|
||||
}
|
||||
server := httptest.NewServer(&handler)
|
||||
client := client.NewOrDie(api.NewContext(), server.URL, "", nil)
|
||||
client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
|
||||
b := binder{client}
|
||||
|
||||
if err := b.Bind(item.binding); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
expectedBody := runtime.EncodeOrDie(latest.Codec, item.binding)
|
||||
handler.ValidateRequest(t, "/api/v1beta1/bindings", "POST", &expectedBody)
|
||||
expectedBody := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), item.binding)
|
||||
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/bindings", "POST", &expectedBody)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func TestClient(t *testing.T) {
|
|||
for apiVersion, values := range testCases {
|
||||
deleteAllEtcdKeys()
|
||||
s := httptest.NewServer(apiserver.Handle(values.Storage, values.Codec, fmt.Sprintf("/api/%s/", apiVersion), values.selfLinker))
|
||||
client := client.NewOrDie(api.NewContext(), s.URL, apiVersion, nil)
|
||||
client := client.NewOrDie(&client.Config{Host: s.URL, Version: apiVersion})
|
||||
|
||||
info, err := client.ServerVersion()
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue