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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
@ -127,8 +126,12 @@ func main() {
|
||||||
Port: *minionPort,
|
Port: *minionPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := api.NewContext()
|
// TODO: expose same flags as client.BindClientConfigFlags but for a server
|
||||||
client, err := client.New(ctx, net.JoinHostPort(*address, strconv.Itoa(int(*port))), *storageVersion, nil)
|
clientConfig := &client.Config{
|
||||||
|
Host: net.JoinHostPort(*address, strconv.Itoa(int(*port))),
|
||||||
|
Version: *storageVersion,
|
||||||
|
}
|
||||||
|
client, err := client.New(clientConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Invalid server address: %v", err)
|
glog.Fatalf("Invalid server address: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"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/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
||||||
|
@ -39,11 +37,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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")
|
||||||
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")
|
||||||
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() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
util.InitLogs()
|
util.InitLogs()
|
||||||
|
@ -51,13 +53,13 @@ func main() {
|
||||||
|
|
||||||
verflag.PrintAndExitIfRequested()
|
verflag.PrintAndExitIfRequested()
|
||||||
|
|
||||||
if len(*master) == 0 {
|
if len(clientConfig.Host) == 0 {
|
||||||
glog.Fatal("usage: controller-manager -master <master>")
|
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 {
|
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)
|
go http.ListenAndServe(net.JoinHostPort(*address, strconv.Itoa(*port)), nil)
|
||||||
|
|
|
@ -29,7 +29,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"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/latest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
|
"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.PollPeriod = time.Second * 1
|
||||||
cl.Sync = true
|
cl.Sync = true
|
||||||
|
|
||||||
|
@ -262,12 +264,10 @@ func runAtomicPutTest(c *client.Client) {
|
||||||
glog.Infof("Posting update (%s, %s)", l, v)
|
glog.Infof("Posting update (%s, %s)", l, v)
|
||||||
err = c.Put().Path("services").Path(svc.ID).Body(&tmpSvc).Do().Error()
|
err = c.Put().Path("services").Path(svc.ID).Body(&tmpSvc).Do().Error()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if se, ok := err.(*client.StatusErr); ok {
|
if errors.IsConflict(err) {
|
||||||
if se.Status.Code == http.StatusConflict {
|
glog.Infof("Conflict: (%s, %s)", l, v)
|
||||||
glog.Infof("Conflict: (%s, %s)", l, v)
|
// This is what we expect.
|
||||||
// This is what we expect.
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
glog.Errorf("Unexpected error putting atomicService: %v", err)
|
glog.Errorf("Unexpected error putting atomicService: %v", err)
|
||||||
continue
|
continue
|
||||||
|
@ -311,7 +311,7 @@ func main() {
|
||||||
// Wait for the synchronization threads to come up.
|
// Wait for the synchronization threads to come up.
|
||||||
time.Sleep(time.Second * 10)
|
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
|
// Run tests in parallel
|
||||||
testFuncs := []testFunc{
|
testFuncs := []testFunc{
|
||||||
|
|
|
@ -43,7 +43,6 @@ import (
|
||||||
var (
|
var (
|
||||||
serverVersion = verflag.Version("server_version", verflag.VersionFalse, "Print the server's version information and quit")
|
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.")
|
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")
|
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")
|
selector = flag.String("l", "", "Selector (label query) to use for listing")
|
||||||
updatePeriod = flag.Duration("u", 60*time.Second, "Update interval period")
|
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")
|
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")
|
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.")
|
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.")
|
clientConfig = &client.Config{}
|
||||||
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.")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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{
|
var parser = kubecfg.NewParser(map[string]runtime.Object{
|
||||||
"pods": &api.Pod{},
|
"pods": &api.Pod{},
|
||||||
"services": &api.Service{},
|
"services": &api.Service{},
|
||||||
|
@ -165,43 +169,29 @@ func main() {
|
||||||
|
|
||||||
verflag.PrintAndExitIfRequested()
|
verflag.PrintAndExitIfRequested()
|
||||||
|
|
||||||
var masterServer string
|
// Initialize the client
|
||||||
if len(*httpServer) > 0 {
|
if clientConfig.Host == "" {
|
||||||
masterServer = *httpServer
|
clientConfig.Host = os.Getenv("KUBERNETES_MASTER")
|
||||||
} else if len(os.Getenv("KUBERNETES_MASTER")) > 0 {
|
|
||||||
masterServer = os.Getenv("KUBERNETES_MASTER")
|
|
||||||
} else {
|
|
||||||
masterServer = "http://localhost:8080"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: get the namespace context when kubecfg ns is completed
|
// 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 clientConfig.Host == "" {
|
||||||
if err != nil {
|
// TODO: eventually apiserver should start on 443 and be secure by default
|
||||||
glog.Fatalf("Can't configure client: %v", err)
|
clientConfig.Host = "http://localhost:8080"
|
||||||
}
|
}
|
||||||
|
if client.IsConfigTransportSecure(clientConfig) {
|
||||||
// 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() {
|
|
||||||
auth, err := kubecfg.LoadAuthInfo(*authConfig, os.Stdin)
|
auth, err := kubecfg.LoadAuthInfo(*authConfig, os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Error loading auth: %v", err)
|
glog.Fatalf("Error loading auth: %v", err)
|
||||||
}
|
}
|
||||||
if *caFile != "" {
|
clientConfig.Username = auth.User
|
||||||
auth.CAFile = *caFile
|
clientConfig.Password = auth.Password
|
||||||
}
|
}
|
||||||
if *certFile != "" {
|
kubeClient, err := client.New(clientConfig)
|
||||||
auth.CertFile = *certFile
|
if err != nil {
|
||||||
}
|
glog.Fatalf("Can't configure client: %v", err)
|
||||||
if *keyFile != "" {
|
|
||||||
auth.KeyFile = *keyFile
|
|
||||||
}
|
|
||||||
kubeClient, err = client.New(ctx, masterServer, *apiVersion, auth)
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("Can't configure client: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *serverVersion != verflag.VersionFalse {
|
if *serverVersion != verflag.VersionFalse {
|
||||||
|
|
|
@ -20,8 +20,6 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"time"
|
"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/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config"
|
||||||
|
@ -33,12 +31,13 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configFile = flag.String("configfile", "/tmp/proxy_config", "Configuration file for the proxy")
|
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
|
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)")
|
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() {
|
func init() {
|
||||||
|
client.BindClientConfigFlags(flag.CommandLine, clientConfig)
|
||||||
flag.Var(&etcdServerList, "etcd_servers", "List of etcd servers to watch (http://ip:port), comma separated (optional)")
|
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()
|
endpointsConfig := config.NewEndpointsConfig()
|
||||||
|
|
||||||
// define api config source
|
// define api config source
|
||||||
if *master != "" {
|
if clientConfig.Host != "" {
|
||||||
glog.Infof("Using api calls to get config %v", *master)
|
glog.Infof("Using api calls to get config %v", clientConfig.Host)
|
||||||
ctx := api.NewContext()
|
client, err := client.New(clientConfig)
|
||||||
//TODO: add auth info
|
|
||||||
client, err := client.New(ctx, *master, latest.OldestVersion, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Invalid -master: %v", err)
|
glog.Fatalf("Invalid API configuration: %v", err)
|
||||||
}
|
}
|
||||||
config.NewSourceAPI(
|
config.NewSourceAPI(
|
||||||
client,
|
client,
|
||||||
|
@ -70,7 +67,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a configuration source that handles configuration from etcd.
|
// 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)
|
glog.Infof("Using etcd servers %v", etcdServerList)
|
||||||
|
|
||||||
// Set up logger for etcd client
|
// 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
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
)
|
)
|
||||||
|
@ -91,207 +82,17 @@ type MinionInterface interface {
|
||||||
ListMinions() (*api.MinionList, error)
|
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 {
|
type Client struct {
|
||||||
*RESTClient
|
*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.
|
// 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) {
|
func (c *Client) ListPods(selector labels.Selector) (result *api.PodList, err error) {
|
||||||
result = &api.PodList{}
|
result = &api.PodList{}
|
||||||
|
|
|
@ -27,8 +27,6 @@ import (
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"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/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
@ -38,73 +36,112 @@ import (
|
||||||
// TODO: Move this to a common place, it's needed in multiple tests.
|
// TODO: Move this to a common place, it's needed in multiple tests.
|
||||||
const apiPath = "/api/v1beta1"
|
const apiPath = "/api/v1beta1"
|
||||||
|
|
||||||
func TestChecksCodec(t *testing.T) {
|
type testRequest struct {
|
||||||
testCases := map[string]struct {
|
Method string
|
||||||
Err bool
|
Path string
|
||||||
Prefix string
|
Header string
|
||||||
Codec runtime.Codec
|
Query url.Values
|
||||||
}{
|
Body runtime.Object
|
||||||
"v1beta1": {false, "/api/v1beta1/", v1beta1.Codec},
|
RawBody *string
|
||||||
"": {false, "/api/v1beta1/", v1beta1.Codec},
|
}
|
||||||
"v1beta2": {false, "/api/v1beta2/", v1beta2.Codec},
|
|
||||||
"v1beta3": {true, "", nil},
|
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()
|
if responseBody := body(c.Response.Body, c.Response.RawBody); responseBody != nil {
|
||||||
for version, expected := range testCases {
|
c.handler.ResponseBody = *responseBody
|
||||||
client, err := New(ctx, "127.0.0.1", version, nil)
|
}
|
||||||
switch {
|
c.server = httptest.NewServer(c.handler)
|
||||||
case err == nil && expected.Err:
|
if c.Client == nil {
|
||||||
t.Errorf("expected error but was nil")
|
c.Client = NewOrDie(&Config{
|
||||||
continue
|
Host: c.server.URL,
|
||||||
case err != nil && !expected.Err:
|
Version: "v1beta1",
|
||||||
t.Errorf("unexpected error %v", err)
|
})
|
||||||
continue
|
}
|
||||||
case err != nil:
|
c.QueryValidator = map[string]func(string, string) bool{}
|
||||||
continue
|
return c
|
||||||
}
|
}
|
||||||
if e, a := expected.Prefix, client.prefix; e != a {
|
|
||||||
t.Errorf("expected %#v, got %#v", e, a)
|
func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) {
|
||||||
}
|
c.ValidateCommon(t, err)
|
||||||
if e, a := expected.Codec, client.Codec; e != a {
|
|
||||||
t.Errorf("expected %#v, got %#v", e, a)
|
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) {
|
func (c *testClient) ValidateRaw(t *testing.T, received []byte, err error) {
|
||||||
testCases := map[string]struct {
|
c.ValidateCommon(t, err)
|
||||||
Host string
|
|
||||||
Prefix string
|
if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
|
||||||
Err bool
|
t.Errorf("bad response for request %#v: expected %s, got %s", c.Request, c.Response.Body, received)
|
||||||
}{
|
|
||||||
"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},
|
|
||||||
}
|
}
|
||||||
ctx := api.NewContext()
|
}
|
||||||
for k, expected := range testCases {
|
|
||||||
c, err := NewRESTClient(ctx, k, nil, "/api/v1beta1/", v1beta1.Codec)
|
func (c *testClient) ValidateCommon(t *testing.T, err error) {
|
||||||
switch {
|
defer c.server.Close()
|
||||||
case err == nil && expected.Err:
|
|
||||||
t.Errorf("expected error but was nil")
|
if c.Error {
|
||||||
continue
|
if err == nil {
|
||||||
case err != nil && !expected.Err:
|
t.Errorf("error expected for %#v, got none", c.Request)
|
||||||
t.Errorf("unexpected error %v", err)
|
|
||||||
continue
|
|
||||||
case err != nil:
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if e, a := expected.Host, c.host; e != a {
|
return
|
||||||
t.Errorf("%s: expected host %s, got %s", k, e, a)
|
}
|
||||||
continue
|
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 {
|
observed := actualQuery.Get(key)
|
||||||
t.Errorf("%s: expected prefix %s, got %s", k, e, a)
|
if !validator(values[0], observed) {
|
||||||
continue
|
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) {
|
func TestListEmptyPods(t *testing.T) {
|
||||||
|
@ -353,109 +390,6 @@ func body(obj runtime.Object, raw *string) *string {
|
||||||
return raw
|
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) {
|
func TestListServices(t *testing.T) {
|
||||||
c := &testClient{
|
c := &testClient{
|
||||||
Request: testRequest{Method: "GET", Path: "/services"},
|
Request: testRequest{Method: "GET", Path: "/services"},
|
||||||
|
@ -517,10 +451,10 @@ func TestGetService(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateService(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"}}},
|
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"}}},
|
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"}})
|
response, err := c.Setup().CreateService(&api.Service{JSONBase: api.JSONBase{ID: "service-1"}})
|
||||||
c.Validate(t, response, err)
|
c.Validate(t, response, err)
|
||||||
}
|
}
|
||||||
|
@ -571,102 +505,6 @@ func TestGetEndpoints(t *testing.T) {
|
||||||
c.Validate(t, response, err)
|
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) {
|
func TestGetServerVersion(t *testing.T) {
|
||||||
expect := version.Info{
|
expect := version.Info{
|
||||||
Major: "foo",
|
Major: "foo",
|
||||||
|
@ -683,7 +521,7 @@ func TestGetServerVersion(t *testing.T) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(output)
|
w.Write(output)
|
||||||
}))
|
}))
|
||||||
client := NewOrDie(api.NewContext(), server.URL, "", nil)
|
client := NewOrDie(&Config{Host: server.URL})
|
||||||
|
|
||||||
got, err := client.ServerVersion()
|
got, err := client.ServerVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -14,6 +14,34 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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
|
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.
|
// are therefore not allowed to set manually.
|
||||||
var specialParams = util.NewStringSet("sync", "timeout")
|
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.
|
// 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
|
// Any errors are stored until the end of your call, so you only have to
|
||||||
// check once.
|
// check once.
|
||||||
|
@ -232,7 +182,8 @@ func (r *Request) PollPeriod(d time.Duration) *Request {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Request) finalURL() string {
|
func (r *Request) finalURL() string {
|
||||||
finalURL := r.c.host + r.path
|
finalURL := *r.c.baseURL
|
||||||
|
finalURL.Path = r.path
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
for key, value := range r.params {
|
for key, value := range r.params {
|
||||||
query.Add(key, value)
|
query.Add(key, value)
|
||||||
|
@ -245,8 +196,8 @@ func (r *Request) finalURL() string {
|
||||||
query.Add("timeout", r.timeout.String())
|
query.Add("timeout", r.timeout.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finalURL += "?" + query.Encode()
|
finalURL.RawQuery = query.Encode()
|
||||||
return finalURL
|
return finalURL.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch attempts to begin watching the requested location.
|
// Watch attempts to begin watching the requested location.
|
||||||
|
@ -259,10 +210,11 @@ func (r *Request) Watch() (watch.Interface, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if r.c.auth != nil {
|
client := r.c.Client
|
||||||
req.SetBasicAuth(r.c.auth.User, r.c.auth.Password)
|
if client == nil {
|
||||||
|
client = http.DefaultClient
|
||||||
}
|
}
|
||||||
response, err := r.c.httpClient.Do(req)
|
response, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -284,10 +236,11 @@ func (r *Request) Do() Result {
|
||||||
}
|
}
|
||||||
respBody, err := r.c.doRequest(req)
|
respBody, err := r.c.doRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if statusErr, ok := err.(*StatusErr); ok {
|
if s, ok := err.(APIStatus); ok {
|
||||||
if statusErr.Status.Status == api.StatusWorking && r.pollPeriod != 0 {
|
status := s.Status()
|
||||||
if statusErr.Status.Details != nil {
|
if status.Status == api.StatusWorking && r.pollPeriod != 0 {
|
||||||
id := statusErr.Status.Details.ID
|
if status.Details != nil {
|
||||||
|
id := status.Details.ID
|
||||||
if len(id) > 0 {
|
if len(id) > 0 {
|
||||||
glog.Infof("Waiting for completion of /operations/%s", id)
|
glog.Infof("Waiting for completion of /operations/%s", id)
|
||||||
time.Sleep(r.pollPeriod)
|
time.Sleep(r.pollPeriod)
|
||||||
|
|
|
@ -48,9 +48,7 @@ func TestDoRequestNewWay(t *testing.T) {
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
testServer := httptest.NewServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
auth := AuthInfo{User: "user", Password: "pass"}
|
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta2", Username: "user", Password: "pass"})
|
||||||
ctx := api.NewContext()
|
|
||||||
c := NewOrDie(ctx, testServer.URL, "v1beta2", &auth)
|
|
||||||
obj, err := c.Verb("POST").
|
obj, err := c.Verb("POST").
|
||||||
Path("foo/bar").
|
Path("foo/bar").
|
||||||
Path("baz").
|
Path("baz").
|
||||||
|
@ -84,9 +82,7 @@ func TestDoRequestNewWayReader(t *testing.T) {
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
testServer := httptest.NewServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
auth := AuthInfo{User: "user", Password: "pass"}
|
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
|
||||||
ctx := api.NewContext()
|
|
||||||
c := NewOrDie(ctx, testServer.URL, "v1beta1", &auth)
|
|
||||||
obj, err := c.Verb("POST").
|
obj, err := c.Verb("POST").
|
||||||
Path("foo/bar").
|
Path("foo/bar").
|
||||||
Path("baz").
|
Path("baz").
|
||||||
|
@ -122,9 +118,7 @@ func TestDoRequestNewWayObj(t *testing.T) {
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
testServer := httptest.NewServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
auth := AuthInfo{User: "user", Password: "pass"}
|
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta2", Username: "user", Password: "pass"})
|
||||||
ctx := api.NewContext()
|
|
||||||
c := NewOrDie(ctx, testServer.URL, "v1beta2", &auth)
|
|
||||||
obj, err := c.Verb("POST").
|
obj, err := c.Verb("POST").
|
||||||
Path("foo/bar").
|
Path("foo/bar").
|
||||||
Path("baz").
|
Path("baz").
|
||||||
|
@ -173,9 +167,7 @@ func TestDoRequestNewWayFile(t *testing.T) {
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
testServer := httptest.NewServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
auth := AuthInfo{User: "user", Password: "pass"}
|
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
|
||||||
ctx := api.NewContext()
|
|
||||||
c := NewOrDie(ctx, testServer.URL, "v1beta1", &auth)
|
|
||||||
obj, err := c.Verb("POST").
|
obj, err := c.Verb("POST").
|
||||||
Path("foo/bar").
|
Path("foo/bar").
|
||||||
Path("baz").
|
Path("baz").
|
||||||
|
@ -200,8 +192,7 @@ func TestDoRequestNewWayFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerbs(t *testing.T) {
|
func TestVerbs(t *testing.T) {
|
||||||
ctx := api.NewContext()
|
c := NewOrDie(&Config{})
|
||||||
c := NewOrDie(ctx, "localhost", "", nil)
|
|
||||||
if r := c.Post(); r.verb != "POST" {
|
if r := c.Post(); r.verb != "POST" {
|
||||||
t.Errorf("Post verb is wrong")
|
t.Errorf("Post verb is wrong")
|
||||||
}
|
}
|
||||||
|
@ -217,9 +208,8 @@ func TestVerbs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAbsPath(t *testing.T) {
|
func TestAbsPath(t *testing.T) {
|
||||||
ctx := api.NewContext()
|
|
||||||
expectedPath := "/bar/foo"
|
expectedPath := "/bar/foo"
|
||||||
c := NewOrDie(ctx, "localhost", "", nil)
|
c := NewOrDie(&Config{})
|
||||||
r := c.Post().Path("/foo").AbsPath(expectedPath)
|
r := c.Post().Path("/foo").AbsPath(expectedPath)
|
||||||
if r.path != expectedPath {
|
if r.path != expectedPath {
|
||||||
t.Errorf("unexpected path: %s, expected %s", 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) {
|
func TestSync(t *testing.T) {
|
||||||
ctx := api.NewContext()
|
c := NewOrDie(&Config{})
|
||||||
c := NewOrDie(ctx, "localhost", "", nil)
|
|
||||||
r := c.Get()
|
r := c.Get()
|
||||||
if r.sync {
|
if r.sync {
|
||||||
t.Errorf("sync has wrong default")
|
t.Errorf("sync has wrong default")
|
||||||
|
@ -254,9 +243,8 @@ func TestUintParam(t *testing.T) {
|
||||||
{"baz", 0, "http://localhost?baz=0"},
|
{"baz", 0, "http://localhost?baz=0"},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := api.NewContext()
|
|
||||||
for _, item := range table {
|
for _, item := range table {
|
||||||
c := NewOrDie(ctx, "localhost", "", nil)
|
c := NewOrDie(&Config{})
|
||||||
r := c.Get().AbsPath("").UintParam(item.name, item.testVal)
|
r := c.Get().AbsPath("").UintParam(item.name, item.testVal)
|
||||||
if e, a := item.expectStr, r.finalURL(); e != a {
|
if e, a := item.expectStr, r.finalURL(); e != a {
|
||||||
t.Errorf("expected %v, got %v", e, a)
|
t.Errorf("expected %v, got %v", e, a)
|
||||||
|
@ -273,9 +261,9 @@ func TestUnacceptableParamNames(t *testing.T) {
|
||||||
{"sync", "foo", false},
|
{"sync", "foo", false},
|
||||||
{"timeout", "42", false},
|
{"timeout", "42", false},
|
||||||
}
|
}
|
||||||
ctx := api.NewContext()
|
|
||||||
for _, item := range table {
|
for _, item := range table {
|
||||||
c := NewOrDie(ctx, "localhost", "", nil)
|
c := NewOrDie(&Config{})
|
||||||
r := c.Get().setParam(item.name, item.testVal)
|
r := c.Get().setParam(item.name, item.testVal)
|
||||||
if e, a := item.expectSuccess, r.err == nil; e != a {
|
if e, a := item.expectSuccess, r.err == nil; e != a {
|
||||||
t.Errorf("expected %v, got %v (%v)", e, a, r.err)
|
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) {
|
func TestSetPollPeriod(t *testing.T) {
|
||||||
ctx := api.NewContext()
|
c := NewOrDie(&Config{})
|
||||||
c := NewOrDie(ctx, "localhost", "", nil)
|
|
||||||
r := c.Get()
|
r := c.Get()
|
||||||
if r.pollPeriod == 0 {
|
if r.pollPeriod == 0 {
|
||||||
t.Errorf("polling should be on by default")
|
t.Errorf("polling should be on by default")
|
||||||
|
@ -315,9 +302,7 @@ func TestPolling(t *testing.T) {
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
auth := AuthInfo{User: "user", Password: "pass"}
|
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
|
||||||
ctx := api.NewContext()
|
|
||||||
c := NewOrDie(ctx, testServer.URL, "v1beta1", &auth)
|
|
||||||
|
|
||||||
trials := []func(){
|
trials := []func(){
|
||||||
func() {
|
func() {
|
||||||
|
@ -342,7 +327,7 @@ func TestPolling(t *testing.T) {
|
||||||
t.Errorf("Unexpected non error: %v", obj)
|
t.Errorf("Unexpected non error: %v", obj)
|
||||||
return
|
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)
|
t.Errorf("Unexpected kind of error: %#v", err)
|
||||||
return
|
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"]
|
auth, ok := r.Header["Authorization"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
|
@ -376,16 +361,16 @@ func authFromReq(r *http.Request) (*AuthInfo, bool) {
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return nil, false
|
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.
|
// checkAuth sets errors if the auth found in r doesn't match the expectation.
|
||||||
// TODO: Move to util, test in more places.
|
// 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)
|
foundAuth, found := authFromReq(r)
|
||||||
if !found {
|
if !found {
|
||||||
t.Errorf("no auth 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)
|
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"}}},
|
{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) {
|
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
checkAuth(t, auth, r)
|
checkAuth(t, auth, r)
|
||||||
flusher, ok := w.(http.Flusher)
|
flusher, ok := w.(http.Flusher)
|
||||||
|
@ -421,8 +406,12 @@ func TestWatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
ctx := api.NewContext()
|
s, err := New(&Config{
|
||||||
s, err := New(ctx, testServer.URL, "v1beta1", &auth)
|
Host: testServer.URL,
|
||||||
|
Version: "v1beta1",
|
||||||
|
Username: "user",
|
||||||
|
Password: "pass",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
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"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -28,7 +29,7 @@ import (
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"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/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
@ -38,11 +39,8 @@ import (
|
||||||
"github.com/coreos/go-etcd/etcd"
|
"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 {
|
func makeURL(suffix string) string {
|
||||||
return apiPath + suffix
|
return path.Join("/api", testapi.Version(), suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakePodControl struct {
|
type FakePodControl struct {
|
||||||
|
@ -116,8 +114,8 @@ func TestSyncReplicationControllerDoesNothing(t *testing.T) {
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
ResponseBody: string(body),
|
ResponseBody: string(body),
|
||||||
}
|
}
|
||||||
testServer := httptest.NewTLSServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||||
|
|
||||||
fakePodControl := FakePodControl{}
|
fakePodControl := FakePodControl{}
|
||||||
|
|
||||||
|
@ -136,8 +134,8 @@ func TestSyncReplicationControllerDeletes(t *testing.T) {
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
ResponseBody: string(body),
|
ResponseBody: string(body),
|
||||||
}
|
}
|
||||||
testServer := httptest.NewTLSServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||||
|
|
||||||
fakePodControl := FakePodControl{}
|
fakePodControl := FakePodControl{}
|
||||||
|
|
||||||
|
@ -151,13 +149,13 @@ func TestSyncReplicationControllerDeletes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSyncReplicationControllerCreates(t *testing.T) {
|
func TestSyncReplicationControllerCreates(t *testing.T) {
|
||||||
body, _ := latest.Codec.Encode(newPodList(0))
|
body := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), newPodList(0))
|
||||||
fakeHandler := util.FakeHandler{
|
fakeHandler := util.FakeHandler{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
ResponseBody: string(body),
|
ResponseBody: string(body),
|
||||||
}
|
}
|
||||||
testServer := httptest.NewTLSServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||||
|
|
||||||
fakePodControl := FakePodControl{}
|
fakePodControl := FakePodControl{}
|
||||||
|
|
||||||
|
@ -171,13 +169,13 @@ func TestSyncReplicationControllerCreates(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateReplica(t *testing.T) {
|
func TestCreateReplica(t *testing.T) {
|
||||||
body, _ := v1beta1.Codec.Encode(&api.Pod{})
|
body := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), &api.Pod{})
|
||||||
fakeHandler := util.FakeHandler{
|
fakeHandler := util.FakeHandler{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
ResponseBody: string(body),
|
ResponseBody: string(body),
|
||||||
}
|
}
|
||||||
testServer := httptest.NewTLSServer(&fakeHandler)
|
testServer := httptest.NewServer(&fakeHandler)
|
||||||
client := client.NewOrDie(api.NewContext(), testServer.URL, "v1beta1", nil)
|
client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
|
||||||
|
|
||||||
podControl := RealPodControl{
|
podControl := RealPodControl{
|
||||||
kubeClient: client,
|
kubeClient: client,
|
||||||
|
@ -211,7 +209,7 @@ func TestCreateReplica(t *testing.T) {
|
||||||
expectedPod := api.Pod{
|
expectedPod := api.Pod{
|
||||||
JSONBase: api.JSONBase{
|
JSONBase: api.JSONBase{
|
||||||
Kind: "Pod",
|
Kind: "Pod",
|
||||||
APIVersion: latest.Version,
|
APIVersion: testapi.Version(),
|
||||||
},
|
},
|
||||||
Labels: controllerSpec.DesiredState.PodTemplate.Labels,
|
Labels: controllerSpec.DesiredState.PodTemplate.Labels,
|
||||||
DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState,
|
DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState,
|
||||||
|
@ -229,7 +227,7 @@ func TestCreateReplica(t *testing.T) {
|
||||||
|
|
||||||
func TestSynchonize(t *testing.T) {
|
func TestSynchonize(t *testing.T) {
|
||||||
controllerSpec1 := api.ReplicationController{
|
controllerSpec1 := api.ReplicationController{
|
||||||
JSONBase: api.JSONBase{APIVersion: "v1beta1"},
|
JSONBase: api.JSONBase{APIVersion: testapi.Version()},
|
||||||
DesiredState: api.ReplicationControllerState{
|
DesiredState: api.ReplicationControllerState{
|
||||||
Replicas: 4,
|
Replicas: 4,
|
||||||
PodTemplate: api.PodTemplate{
|
PodTemplate: api.PodTemplate{
|
||||||
|
@ -250,7 +248,7 @@ func TestSynchonize(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
controllerSpec2 := api.ReplicationController{
|
controllerSpec2 := api.ReplicationController{
|
||||||
JSONBase: api.JSONBase{APIVersion: "v1beta1"},
|
JSONBase: api.JSONBase{APIVersion: testapi.Version()},
|
||||||
DesiredState: api.ReplicationControllerState{
|
DesiredState: api.ReplicationControllerState{
|
||||||
Replicas: 3,
|
Replicas: 3,
|
||||||
PodTemplate: api.PodTemplate{
|
PodTemplate: api.PodTemplate{
|
||||||
|
@ -289,7 +287,7 @@ func TestSynchonize(t *testing.T) {
|
||||||
|
|
||||||
fakePodHandler := util.FakeHandler{
|
fakePodHandler := util.FakeHandler{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
ResponseBody: "{\"apiVersion\": \"" + latest.Version + "\", \"kind\": \"PodList\"}",
|
ResponseBody: "{\"apiVersion\": \"" + testapi.Version() + "\", \"kind\": \"PodList\"}",
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
fakeControllerHandler := util.FakeHandler{
|
fakeControllerHandler := util.FakeHandler{
|
||||||
|
@ -303,14 +301,14 @@ func TestSynchonize(t *testing.T) {
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle("/api/v1beta1/pods/", &fakePodHandler)
|
mux.Handle("/api/"+testapi.Version()+"/pods/", &fakePodHandler)
|
||||||
mux.Handle("/api/v1beta1/replicationControllers/", &fakeControllerHandler)
|
mux.Handle("/api/"+testapi.Version()+"/replicationControllers/", &fakeControllerHandler)
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
t.Errorf("Unexpected request for %v", req.RequestURI)
|
t.Errorf("Unexpected request for %v", req.RequestURI)
|
||||||
})
|
})
|
||||||
testServer := httptest.NewServer(mux)
|
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)
|
manager := NewReplicationManager(client)
|
||||||
fakePodControl := FakePodControl{}
|
fakePodControl := FakePodControl{}
|
||||||
manager.podControl = &fakePodControl
|
manager.podControl = &fakePodControl
|
||||||
|
|
|
@ -51,9 +51,14 @@ func promptForString(field string, r io.Reader) string {
|
||||||
return result
|
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.
|
// 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) {
|
func LoadAuthInfo(path string, r io.Reader) (*AuthInfo, error) {
|
||||||
var auth client.AuthInfo
|
var auth AuthInfo
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
auth.User = promptForString("Username", r)
|
auth.User = promptForString("Username", r)
|
||||||
auth.Password = promptForString("Password", r)
|
auth.Password = promptForString("Password", r)
|
||||||
|
|
|
@ -222,12 +222,12 @@ func TestCloudCfgDeleteControllerWithReplicas(t *testing.T) {
|
||||||
func TestLoadAuthInfo(t *testing.T) {
|
func TestLoadAuthInfo(t *testing.T) {
|
||||||
loadAuthInfoTests := []struct {
|
loadAuthInfoTests := []struct {
|
||||||
authData string
|
authData string
|
||||||
authInfo *client.AuthInfo
|
authInfo *AuthInfo
|
||||||
r io.Reader
|
r io.Reader
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
`{"user": "user", "password": "pass"}`,
|
`{"user": "user", "password": "pass"}`,
|
||||||
&client.AuthInfo{User: "user", Password: "pass"},
|
&AuthInfo{User: "user", Password: "pass"},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -235,7 +235,7 @@ func TestLoadAuthInfo(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"missing",
|
"missing",
|
||||||
&client.AuthInfo{User: "user", Password: "pass"},
|
&AuthInfo{User: "user", Password: "pass"},
|
||||||
bytes.NewBufferString("user\npass"),
|
bytes.NewBufferString("user\npass"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import (
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
_ "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/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
@ -37,7 +37,7 @@ func newPodList(count int) api.PodList {
|
||||||
pods = append(pods, api.Pod{
|
pods = append(pods, api.Pod{
|
||||||
JSONBase: api.JSONBase{
|
JSONBase: api.JSONBase{
|
||||||
ID: fmt.Sprintf("pod%d", i),
|
ID: fmt.Sprintf("pod%d", i),
|
||||||
APIVersion: "v1beta1",
|
APIVersion: testapi.Version(),
|
||||||
},
|
},
|
||||||
DesiredState: api.PodState{
|
DesiredState: api.PodState{
|
||||||
Manifest: api.ContainerManifest{
|
Manifest: api.ContainerManifest{
|
||||||
|
@ -58,7 +58,7 @@ func newPodList(count int) api.PodList {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return api.PodList{
|
return api.PodList{
|
||||||
JSONBase: api.JSONBase{APIVersion: "v1beta1", Kind: "PodList"},
|
JSONBase: api.JSONBase{APIVersion: testapi.Version(), Kind: "PodList"},
|
||||||
Items: pods,
|
Items: pods,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,10 +143,10 @@ func makeTestServer(t *testing.T, podResponse serverResponse, serviceResponse se
|
||||||
ResponseBody: util.EncodeJSON(endpointsResponse.obj),
|
ResponseBody: util.EncodeJSON(endpointsResponse.obj),
|
||||||
}
|
}
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle("/api/v1beta1/pods", &fakePodHandler)
|
mux.Handle("/api/"+testapi.Version()+"/pods", &fakePodHandler)
|
||||||
mux.Handle("/api/v1beta1/services", &fakeServiceHandler)
|
mux.Handle("/api/"+testapi.Version()+"/services", &fakeServiceHandler)
|
||||||
mux.Handle("/api/v1beta1/endpoints", &fakeEndpointsHandler)
|
mux.Handle("/api/"+testapi.Version()+"/endpoints", &fakeEndpointsHandler)
|
||||||
mux.Handle("/api/v1beta1/endpoints/", &fakeEndpointsHandler)
|
mux.Handle("/api/"+testapi.Version()+"/endpoints/", &fakeEndpointsHandler)
|
||||||
mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
|
||||||
t.Errorf("unexpected request: %v", req.RequestURI)
|
t.Errorf("unexpected request: %v", req.RequestURI)
|
||||||
res.WriteHeader(http.StatusNotFound)
|
res.WriteHeader(http.StatusNotFound)
|
||||||
|
@ -159,7 +159,7 @@ func TestSyncEndpointsEmpty(t *testing.T) {
|
||||||
serverResponse{http.StatusOK, newPodList(0)},
|
serverResponse{http.StatusOK, newPodList(0)},
|
||||||
serverResponse{http.StatusOK, api.ServiceList{}},
|
serverResponse{http.StatusOK, api.ServiceList{}},
|
||||||
serverResponse{http.StatusOK, api.Endpoints{}})
|
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{}
|
serviceRegistry := registrytest.ServiceRegistry{}
|
||||||
endpoints := NewEndpointController(&serviceRegistry, client)
|
endpoints := NewEndpointController(&serviceRegistry, client)
|
||||||
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
||||||
|
@ -172,7 +172,7 @@ func TestSyncEndpointsError(t *testing.T) {
|
||||||
serverResponse{http.StatusOK, newPodList(0)},
|
serverResponse{http.StatusOK, newPodList(0)},
|
||||||
serverResponse{http.StatusInternalServerError, api.ServiceList{}},
|
serverResponse{http.StatusInternalServerError, api.ServiceList{}},
|
||||||
serverResponse{http.StatusOK, api.Endpoints{}})
|
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{
|
serviceRegistry := registrytest.ServiceRegistry{
|
||||||
Err: fmt.Errorf("test error"),
|
Err: fmt.Errorf("test error"),
|
||||||
}
|
}
|
||||||
|
@ -203,20 +203,20 @@ func TestSyncEndpointsItemsPreexisting(t *testing.T) {
|
||||||
},
|
},
|
||||||
Endpoints: []string{"6.7.8.9:1000"},
|
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{}
|
serviceRegistry := registrytest.ServiceRegistry{}
|
||||||
endpoints := NewEndpointController(&serviceRegistry, client)
|
endpoints := NewEndpointController(&serviceRegistry, client)
|
||||||
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
data := runtime.EncodeOrDie(v1beta1.Codec, &api.Endpoints{
|
data := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), &api.Endpoints{
|
||||||
JSONBase: api.JSONBase{
|
JSONBase: api.JSONBase{
|
||||||
ID: "foo",
|
ID: "foo",
|
||||||
ResourceVersion: 1,
|
ResourceVersion: 1,
|
||||||
},
|
},
|
||||||
Endpoints: []string{"1.2.3.4:8080"},
|
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) {
|
func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) {
|
||||||
|
@ -239,13 +239,13 @@ func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) {
|
||||||
},
|
},
|
||||||
Endpoints: []string{"1.2.3.4:8080"},
|
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{}
|
serviceRegistry := registrytest.ServiceRegistry{}
|
||||||
endpoints := NewEndpointController(&serviceRegistry, client)
|
endpoints := NewEndpointController(&serviceRegistry, client)
|
||||||
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
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) {
|
func TestSyncEndpointsItems(t *testing.T) {
|
||||||
|
@ -263,19 +263,19 @@ func TestSyncEndpointsItems(t *testing.T) {
|
||||||
serverResponse{http.StatusOK, newPodList(1)},
|
serverResponse{http.StatusOK, newPodList(1)},
|
||||||
serverResponse{http.StatusOK, serviceList},
|
serverResponse{http.StatusOK, serviceList},
|
||||||
serverResponse{http.StatusOK, api.Endpoints{}})
|
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{}
|
serviceRegistry := registrytest.ServiceRegistry{}
|
||||||
endpoints := NewEndpointController(&serviceRegistry, client)
|
endpoints := NewEndpointController(&serviceRegistry, client)
|
||||||
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
if err := endpoints.SyncServiceEndpoints(); err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
data := runtime.EncodeOrDie(v1beta1.Codec, &api.Endpoints{
|
data := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), &api.Endpoints{
|
||||||
JSONBase: api.JSONBase{
|
JSONBase: api.JSONBase{
|
||||||
ResourceVersion: 0,
|
ResourceVersion: 0,
|
||||||
},
|
},
|
||||||
Endpoints: []string{"1.2.3.4:8080"},
|
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) {
|
func TestSyncEndpointsPodError(t *testing.T) {
|
||||||
|
@ -292,7 +292,7 @@ func TestSyncEndpointsPodError(t *testing.T) {
|
||||||
serverResponse{http.StatusInternalServerError, api.PodList{}},
|
serverResponse{http.StatusInternalServerError, api.PodList{}},
|
||||||
serverResponse{http.StatusOK, serviceList},
|
serverResponse{http.StatusOK, serviceList},
|
||||||
serverResponse{http.StatusOK, api.Endpoints{}})
|
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{
|
serviceRegistry := registrytest.ServiceRegistry{
|
||||||
List: api.ServiceList{
|
List: api.ServiceList{
|
||||||
Items: []api.Service{
|
Items: []api.Service{
|
||||||
|
|
|
@ -65,6 +65,10 @@ func (f FakeHandler) ValidateRequest(t TestInterface, expectedPath, expectedMeth
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Couldn't parse %v as a URL.", expectedPath)
|
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 {
|
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)
|
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"
|
"net/http"
|
||||||
"strconv"
|
"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/client"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
||||||
masterPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
masterPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||||
|
@ -35,11 +33,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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")
|
||||||
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")
|
||||||
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() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
util.InitLogs()
|
util.InitLogs()
|
||||||
|
@ -47,11 +49,9 @@ func main() {
|
||||||
|
|
||||||
verflag.PrintAndExitIfRequested()
|
verflag.PrintAndExitIfRequested()
|
||||||
|
|
||||||
// TODO: security story for plugins!
|
kubeClient, err := client.New(clientConfig)
|
||||||
ctx := api.NewContext()
|
|
||||||
kubeClient, err := client.New(ctx, *master, latest.OldestVersion, nil)
|
|
||||||
if err != nil {
|
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)
|
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"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"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"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
@ -39,7 +40,7 @@ func TestCreate(t *testing.T) {
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
server := httptest.NewServer(&handler)
|
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 := ConfigFactory{client}
|
||||||
factory.Create()
|
factory.Create()
|
||||||
}
|
}
|
||||||
|
@ -52,17 +53,17 @@ func TestCreateLists(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
// Minion
|
// Minion
|
||||||
{
|
{
|
||||||
location: "/api/v1beta1/minions?fields=",
|
location: "/api/" + testapi.Version() + "/minions?fields=",
|
||||||
factory: factory.createMinionLW,
|
factory: factory.createMinionLW,
|
||||||
},
|
},
|
||||||
// Assigned pod
|
// Assigned pod
|
||||||
{
|
{
|
||||||
location: "/api/v1beta1/pods?fields=DesiredState.Host!%3D",
|
location: "/api/" + testapi.Version() + "/pods?fields=DesiredState.Host!%3D",
|
||||||
factory: factory.createAssignedPodLW,
|
factory: factory.createAssignedPodLW,
|
||||||
},
|
},
|
||||||
// Unassigned pod
|
// Unassigned pod
|
||||||
{
|
{
|
||||||
location: "/api/v1beta1/pods?fields=DesiredState.Host%3D",
|
location: "/api/" + testapi.Version() + "/pods?fields=DesiredState.Host%3D",
|
||||||
factory: factory.createUnassignedPodLW,
|
factory: factory.createUnassignedPodLW,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -74,7 +75,7 @@ func TestCreateLists(t *testing.T) {
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
server := httptest.NewServer(&handler)
|
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.
|
// This test merely tests that the correct request is made.
|
||||||
item.factory().List()
|
item.factory().List()
|
||||||
handler.ValidateRequest(t, item.location, "GET", nil)
|
handler.ValidateRequest(t, item.location, "GET", nil)
|
||||||
|
@ -91,31 +92,31 @@ func TestCreateWatches(t *testing.T) {
|
||||||
// Minion watch
|
// Minion watch
|
||||||
{
|
{
|
||||||
rv: 0,
|
rv: 0,
|
||||||
location: "/api/v1beta1/watch/minions?fields=&resourceVersion=0",
|
location: "/api/" + testapi.Version() + "/watch/minions?fields=&resourceVersion=0",
|
||||||
factory: factory.createMinionLW,
|
factory: factory.createMinionLW,
|
||||||
}, {
|
}, {
|
||||||
rv: 42,
|
rv: 42,
|
||||||
location: "/api/v1beta1/watch/minions?fields=&resourceVersion=42",
|
location: "/api/" + testapi.Version() + "/watch/minions?fields=&resourceVersion=42",
|
||||||
factory: factory.createMinionLW,
|
factory: factory.createMinionLW,
|
||||||
},
|
},
|
||||||
// Assigned pod watches
|
// Assigned pod watches
|
||||||
{
|
{
|
||||||
rv: 0,
|
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,
|
factory: factory.createAssignedPodLW,
|
||||||
}, {
|
}, {
|
||||||
rv: 42,
|
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,
|
factory: factory.createAssignedPodLW,
|
||||||
},
|
},
|
||||||
// Unassigned pod watches
|
// Unassigned pod watches
|
||||||
{
|
{
|
||||||
rv: 0,
|
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,
|
factory: factory.createUnassignedPodLW,
|
||||||
}, {
|
}, {
|
||||||
rv: 42,
|
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,
|
factory: factory.createUnassignedPodLW,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -127,7 +128,7 @@ func TestCreateWatches(t *testing.T) {
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
server := httptest.NewServer(&handler)
|
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.
|
// This test merely tests that the correct request is made.
|
||||||
item.factory().Watch(item.rv)
|
item.factory().Watch(item.rv)
|
||||||
handler.ValidateRequest(t, item.location, "GET", nil)
|
handler.ValidateRequest(t, item.location, "GET", nil)
|
||||||
|
@ -155,9 +156,9 @@ func TestPollMinions(t *testing.T) {
|
||||||
}
|
}
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
// FakeHandler musn't be sent requests other than the one you want to test.
|
// 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)
|
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}
|
cf := ConfigFactory{client}
|
||||||
|
|
||||||
ce, err := cf.pollMinions()
|
ce, err := cf.pollMinions()
|
||||||
|
@ -165,7 +166,7 @@ func TestPollMinions(t *testing.T) {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
continue
|
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 {
|
if e, a := len(item.minions), ce.Len(); e != a {
|
||||||
t.Errorf("Expected %v, got %v", e, a)
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
@ -182,9 +183,9 @@ func TestDefaultErrorFunc(t *testing.T) {
|
||||||
}
|
}
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
// FakeHandler musn't be sent requests other than the one you want to test.
|
// 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)
|
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()
|
queue := cache.NewFIFO()
|
||||||
errFunc := factory.makeDefaultErrorFunc(queue)
|
errFunc := factory.makeDefaultErrorFunc(queue)
|
||||||
|
|
||||||
|
@ -198,7 +199,7 @@ func TestDefaultErrorFunc(t *testing.T) {
|
||||||
if !exists {
|
if !exists {
|
||||||
continue
|
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) {
|
if e, a := testPod, got; !reflect.DeepEqual(e, a) {
|
||||||
t.Errorf("Expected %v, got %v", e, a)
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
}
|
}
|
||||||
|
@ -289,14 +290,14 @@ func TestBind(t *testing.T) {
|
||||||
T: t,
|
T: t,
|
||||||
}
|
}
|
||||||
server := httptest.NewServer(&handler)
|
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}
|
b := binder{client}
|
||||||
|
|
||||||
if err := b.Bind(item.binding); err != nil {
|
if err := b.Bind(item.binding); err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
expectedBody := runtime.EncodeOrDie(latest.Codec, item.binding)
|
expectedBody := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), item.binding)
|
||||||
handler.ValidateRequest(t, "/api/v1beta1/bindings", "POST", &expectedBody)
|
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/bindings", "POST", &expectedBody)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func TestClient(t *testing.T) {
|
||||||
for apiVersion, values := range testCases {
|
for apiVersion, values := range testCases {
|
||||||
deleteAllEtcdKeys()
|
deleteAllEtcdKeys()
|
||||||
s := httptest.NewServer(apiserver.Handle(values.Storage, values.Codec, fmt.Sprintf("/api/%s/", apiVersion), values.selfLinker))
|
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()
|
info, err := client.ServerVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue