2014-09-30 00:15:00 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
2014-09-30 00:15:00 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2015-08-12 17:35:07 +00:00
|
|
|
package unversioned
|
2014-09-30 00:15:00 +00:00
|
|
|
|
|
|
|
import (
|
2015-10-01 21:56:22 +00:00
|
|
|
"encoding/json"
|
2014-09-30 00:15:00 +00:00
|
|
|
"fmt"
|
2015-05-29 21:31:00 +00:00
|
|
|
"io/ioutil"
|
2015-02-10 13:19:54 +00:00
|
|
|
"net"
|
2014-09-30 00:15:00 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2015-02-04 21:35:53 +00:00
|
|
|
"os"
|
2014-09-30 00:15:00 +00:00
|
|
|
"path"
|
2015-01-07 20:59:22 +00:00
|
|
|
"reflect"
|
2015-02-04 21:35:53 +00:00
|
|
|
gruntime "runtime"
|
2014-09-30 00:15:00 +00:00
|
|
|
"strings"
|
2015-07-24 17:55:11 +00:00
|
|
|
"sync"
|
2015-02-10 13:19:54 +00:00
|
|
|
"time"
|
2014-09-30 00:15:00 +00:00
|
|
|
|
2015-08-05 22:05:17 +00:00
|
|
|
"github.com/golang/glog"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
|
|
"k8s.io/kubernetes/pkg/api/latest"
|
|
|
|
"k8s.io/kubernetes/pkg/runtime"
|
|
|
|
"k8s.io/kubernetes/pkg/util"
|
2015-09-09 17:45:01 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/sets"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/version"
|
2014-09-30 00:15:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
2014-10-03 02:03:52 +00:00
|
|
|
Host string
|
|
|
|
// Prefix is the sub path of the server. If not specified, the client will set
|
|
|
|
// a default value. Use "/" to indicate the server root should be used
|
|
|
|
Prefix string
|
2015-01-06 17:36:08 +00:00
|
|
|
// Version is the API version to talk to. Must be provided when initializing
|
|
|
|
// a RESTClient directly. When initializing a Client, will be set with the default
|
|
|
|
// code version.
|
2014-09-30 00:15:00 +00:00
|
|
|
Version string
|
2015-01-06 17:36:08 +00:00
|
|
|
// Codec specifies the encoding and decoding behavior for runtime.Objects passed
|
|
|
|
// to a RESTClient or Client. Required when initializing a RESTClient, optional
|
|
|
|
// when initializing a Client.
|
|
|
|
Codec runtime.Codec
|
2014-09-30 00:15:00 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2015-01-29 22:43:09 +00:00
|
|
|
// TLSClientConfig contains settings to enable transport layer security
|
|
|
|
TLSClientConfig
|
2014-09-30 00:15:00 +00:00
|
|
|
|
|
|
|
// Server should be accessed without verifying the TLS
|
|
|
|
// certificate. For testing only.
|
|
|
|
Insecure bool
|
|
|
|
|
2015-02-04 21:35:53 +00:00
|
|
|
// UserAgent is an optional field that specifies the caller of this request.
|
|
|
|
UserAgent string
|
|
|
|
|
2014-09-30 00:15:00 +00:00
|
|
|
// Transport may be used for custom HTTP behavior. This attribute may not
|
2015-04-11 16:45:30 +00:00
|
|
|
// be specified with the TLS client certificate options. Use WrapTransport
|
|
|
|
// for most client level operations.
|
2014-09-30 00:15:00 +00:00
|
|
|
Transport http.RoundTripper
|
2015-04-11 16:45:30 +00:00
|
|
|
// WrapTransport will be invoked for custom HTTP behavior after the underlying
|
|
|
|
// transport is initialized (either the transport created from TLSClientConfig,
|
|
|
|
// Transport, or http.DefaultTransport). The config may layer other RoundTrippers
|
|
|
|
// on top of the returned RoundTripper.
|
|
|
|
WrapTransport func(rt http.RoundTripper) http.RoundTripper
|
2015-03-31 03:21:21 +00:00
|
|
|
|
|
|
|
// QPS indicates the maximum QPS to the master from this client. If zero, QPS is unlimited.
|
|
|
|
QPS float32
|
2015-04-09 17:12:52 +00:00
|
|
|
|
|
|
|
// Maximum burst for throttle
|
|
|
|
Burst int
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
|
|
|
|
2014-10-10 22:19:23 +00:00
|
|
|
type KubeletConfig struct {
|
|
|
|
// ToDo: Add support for different kubelet instances exposing different ports
|
|
|
|
Port uint
|
|
|
|
EnableHttps bool
|
|
|
|
|
2015-01-29 22:43:09 +00:00
|
|
|
// TLSClientConfig contains settings to enable transport layer security
|
|
|
|
TLSClientConfig
|
2015-03-19 01:11:39 +00:00
|
|
|
|
|
|
|
// HTTPTimeout is used by the client to timeout http requests to Kubelet.
|
|
|
|
HTTPTimeout time.Duration
|
2015-05-29 22:33:22 +00:00
|
|
|
|
|
|
|
// Dial is a custom dialer used for the client
|
|
|
|
Dial func(net, addr string) (net.Conn, error)
|
2015-01-29 22:43:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TLSClientConfig contains settings to enable transport layer security
|
|
|
|
type TLSClientConfig struct {
|
|
|
|
// Server requires TLS client certificate authentication
|
2014-10-10 22:19:23 +00:00
|
|
|
CertFile string
|
2015-01-29 22:43:09 +00:00
|
|
|
// Server requires TLS client certificate authentication
|
2014-10-10 22:19:23 +00:00
|
|
|
KeyFile string
|
2015-01-29 22:43:09 +00:00
|
|
|
// Trusted root certificates for server
|
2014-10-10 22:19:23 +00:00
|
|
|
CAFile string
|
2015-01-16 16:51:53 +00:00
|
|
|
|
|
|
|
// CertData holds PEM-encoded bytes (typically read from a client certificate file).
|
|
|
|
// CertData takes precedence over CertFile
|
|
|
|
CertData []byte
|
|
|
|
// KeyData holds PEM-encoded bytes (typically read from a client certificate key file).
|
|
|
|
// KeyData takes precedence over KeyFile
|
|
|
|
KeyData []byte
|
|
|
|
// CAData holds PEM-encoded bytes (typically read from a root certificates bundle).
|
|
|
|
// CAData takes precedence over CAFile
|
|
|
|
CAData []byte
|
2014-10-10 22:19:23 +00:00
|
|
|
}
|
|
|
|
|
2014-09-30 00:15:00 +00:00
|
|
|
// New creates a Kubernetes client for the given config. This client works with pods,
|
2015-08-07 06:29:31 +00:00
|
|
|
// replication controllers, daemons, and services. It allows operations such as list, get, update
|
2014-09-30 00:15:00 +00:00
|
|
|
// and delete on these objects. An error is returned if the provided configuration
|
|
|
|
// is not valid.
|
|
|
|
func New(c *Config) (*Client, error) {
|
2014-10-03 02:03:52 +00:00
|
|
|
config := *c
|
2015-01-06 17:36:08 +00:00
|
|
|
if err := SetKubernetesDefaults(&config); err != nil {
|
|
|
|
return nil, err
|
2014-10-03 02:03:52 +00:00
|
|
|
}
|
|
|
|
client, err := RESTClientFor(&config)
|
2014-09-30 00:15:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-09-11 22:46:18 +00:00
|
|
|
|
2015-09-12 00:20:02 +00:00
|
|
|
if _, err := latest.Group("experimental"); err != nil {
|
2015-09-11 22:46:18 +00:00
|
|
|
return &Client{RESTClient: client, ExperimentalClient: nil}, nil
|
|
|
|
}
|
2015-08-31 16:29:18 +00:00
|
|
|
experimentalConfig := *c
|
|
|
|
experimentalClient, err := NewExperimental(&experimentalConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &Client{RESTClient: client, ExperimentalClient: experimentalClient}, nil
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
|
|
|
|
2015-06-13 02:06:18 +00:00
|
|
|
// MatchesServerVersion queries the server to compares the build version
|
|
|
|
// (git hash) of the client with the server's build version. It returns an error
|
|
|
|
// if it failed to contact the server or if the versions are not an exact match.
|
2015-06-17 03:04:51 +00:00
|
|
|
func MatchesServerVersion(client *Client, c *Config) error {
|
|
|
|
var err error
|
|
|
|
if client == nil {
|
|
|
|
client, err = New(c)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-01-07 20:59:22 +00:00
|
|
|
}
|
|
|
|
clientVersion := version.Get()
|
|
|
|
serverVersion, err := client.ServerVersion()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("couldn't read version from server: %v\n", err)
|
|
|
|
}
|
|
|
|
if s := *serverVersion; !reflect.DeepEqual(clientVersion, s) {
|
|
|
|
return fmt.Errorf("server version (%#v) differs from client version (%#v)!\n", s, clientVersion)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-10-01 21:56:22 +00:00
|
|
|
func extractGroupVersions(l *api.APIGroupList) []string {
|
|
|
|
var groupVersions []string
|
|
|
|
for _, g := range l.Groups {
|
|
|
|
for _, gv := range g.Versions {
|
|
|
|
groupVersions = append(groupVersions, gv.GroupVersion)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return groupVersions
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServerAPIVersions returns the GroupVersions supported by the API server.
|
|
|
|
// It creates a RESTClient based on the passed in config, but it doesn't rely
|
|
|
|
// on the Version, Codec, and Prefix of the config, because it uses AbsPath and
|
|
|
|
// takes the raw response.
|
|
|
|
func ServerAPIVersions(c *Config) (groupVersions []string, err error) {
|
2015-10-01 23:23:01 +00:00
|
|
|
transport, err := TransportFor(c)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
client := http.Client{Transport: transport}
|
|
|
|
|
|
|
|
configCopy := *c
|
|
|
|
configCopy.Version = ""
|
|
|
|
configCopy.Prefix = ""
|
|
|
|
baseURL, err := defaultServerUrlFor(c)
|
2015-10-01 21:56:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Get the groupVersions exposed at /api
|
2015-10-01 23:23:01 +00:00
|
|
|
req, err := http.NewRequest("GET", baseURL.String()+"/api", nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
2015-10-01 21:56:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var v api.APIVersions
|
|
|
|
err = json.Unmarshal(body, &v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("got '%s': %v", string(body), err)
|
|
|
|
}
|
|
|
|
groupVersions = append(groupVersions, v.Versions...)
|
|
|
|
// Get the groupVersions exposed at /apis
|
2015-10-01 23:23:01 +00:00
|
|
|
req, err = http.NewRequest("GET", baseURL.String()+"/apis", nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
resp, err = client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
body, err = ioutil.ReadAll(resp.Body)
|
2015-10-01 21:56:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var apiGroupList api.APIGroupList
|
|
|
|
err = json.Unmarshal(body, &apiGroupList)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("got '%s': %v", string(body), err)
|
|
|
|
}
|
|
|
|
groupVersions = append(groupVersions, extractGroupVersions(&apiGroupList)...)
|
|
|
|
|
|
|
|
return groupVersions, nil
|
|
|
|
}
|
|
|
|
|
2015-06-13 02:06:18 +00:00
|
|
|
// NegotiateVersion queries the server's supported api versions to find
|
|
|
|
// a version that both client and server support.
|
2015-06-17 03:04:51 +00:00
|
|
|
// - If no version is provided, try registered client versions in order of
|
2015-06-13 02:06:18 +00:00
|
|
|
// preference.
|
|
|
|
// - If version is provided, but not default config (explicitly requested via
|
|
|
|
// commandline flag), and is unsupported by the server, print a warning to
|
|
|
|
// stderr and try client's registered versions in order of preference.
|
|
|
|
// - If version is config default, and the server does not support it,
|
|
|
|
// return an error.
|
2015-06-30 02:30:14 +00:00
|
|
|
func NegotiateVersion(client *Client, c *Config, version string, clientRegisteredVersions []string) (string, error) {
|
2015-06-17 03:04:51 +00:00
|
|
|
var err error
|
|
|
|
if client == nil {
|
|
|
|
client, err = New(c)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2015-06-13 02:06:18 +00:00
|
|
|
}
|
2015-09-09 17:45:01 +00:00
|
|
|
clientVersions := sets.String{}
|
2015-06-30 02:30:14 +00:00
|
|
|
for _, v := range clientRegisteredVersions {
|
2015-06-13 02:06:18 +00:00
|
|
|
clientVersions.Insert(v)
|
|
|
|
}
|
|
|
|
apiVersions, err := client.ServerAPIVersions()
|
|
|
|
if err != nil {
|
2015-08-09 15:41:52 +00:00
|
|
|
return "", fmt.Errorf("couldn't read version from server: %v", err)
|
2015-06-13 02:06:18 +00:00
|
|
|
}
|
2015-09-09 17:45:01 +00:00
|
|
|
serverVersions := sets.String{}
|
2015-06-13 02:06:18 +00:00
|
|
|
for _, v := range apiVersions.Versions {
|
|
|
|
serverVersions.Insert(v)
|
|
|
|
}
|
|
|
|
// If no version requested, use config version (may also be empty).
|
|
|
|
if len(version) == 0 {
|
|
|
|
version = c.Version
|
|
|
|
}
|
|
|
|
// If version explicitly requested verify that both client and server support it.
|
|
|
|
// If server does not support warn, but try to negotiate a lower version.
|
|
|
|
if len(version) != 0 {
|
|
|
|
if !clientVersions.Has(version) {
|
|
|
|
return "", fmt.Errorf("Client does not support API version '%s'. Client supported API versions: %v", version, clientVersions)
|
|
|
|
|
|
|
|
}
|
|
|
|
if serverVersions.Has(version) {
|
|
|
|
return version, nil
|
|
|
|
}
|
|
|
|
// If we are using an explicit config version the server does not support, fail.
|
|
|
|
if version == c.Version {
|
|
|
|
return "", fmt.Errorf("Server does not support API version '%s'.", version)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-30 02:30:14 +00:00
|
|
|
for _, clientVersion := range clientRegisteredVersions {
|
2015-06-13 02:06:18 +00:00
|
|
|
if serverVersions.Has(clientVersion) {
|
|
|
|
// Version was not explicitly requested in command config (--api-version).
|
|
|
|
// Ok to fall back to a supported version with a warning.
|
2015-10-01 04:18:50 +00:00
|
|
|
// TODO: caesarxuchao: enable the warning message when we have
|
|
|
|
// proper fix. Please refer to issue #14895.
|
|
|
|
// if len(version) != 0 {
|
|
|
|
// glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion)
|
|
|
|
// }
|
2015-06-13 02:06:18 +00:00
|
|
|
return clientVersion, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", fmt.Errorf("Failed to negotiate an api version. Server supports: %v. Client supports: %v.",
|
2015-06-30 02:30:14 +00:00
|
|
|
serverVersions, clientRegisteredVersions)
|
2015-06-13 02:06:18 +00:00
|
|
|
}
|
|
|
|
|
2014-09-30 00:15:00 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2015-05-29 21:31:00 +00:00
|
|
|
// InClusterConfig returns a config object which uses the service account
|
|
|
|
// kubernetes gives to pods. It's intended for clients that expect to be
|
|
|
|
// running inside a pod running on kuberenetes. It will return an error if
|
|
|
|
// called from a process not running in a kubernetes environment.
|
|
|
|
func InClusterConfig() (*Config, error) {
|
2015-06-24 03:54:19 +00:00
|
|
|
token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountTokenKey)
|
2015-05-29 21:31:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-06-24 03:54:19 +00:00
|
|
|
tlsClientConfig := TLSClientConfig{}
|
|
|
|
rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountRootCAKey
|
|
|
|
if _, err := util.CertPoolFromFile(rootCAFile); err != nil {
|
2015-09-29 07:04:07 +00:00
|
|
|
glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
|
2015-06-24 03:54:19 +00:00
|
|
|
} else {
|
|
|
|
tlsClientConfig.CAFile = rootCAFile
|
|
|
|
}
|
|
|
|
|
2015-05-29 21:31:00 +00:00
|
|
|
return &Config{
|
|
|
|
// TODO: switch to using cluster DNS.
|
2015-06-24 03:54:19 +00:00
|
|
|
Host: "https://" + net.JoinHostPort(os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")),
|
|
|
|
BearerToken: string(token),
|
|
|
|
TLSClientConfig: tlsClientConfig,
|
2015-05-29 21:31:00 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInCluster is a shortcut for calling InClusterConfig() and then New().
|
|
|
|
func NewInCluster() (*Client, error) {
|
|
|
|
cc, err := InClusterConfig()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return New(cc)
|
|
|
|
}
|
|
|
|
|
2015-01-06 17:36:08 +00:00
|
|
|
// SetKubernetesDefaults sets default values on the provided client config for accessing the
|
|
|
|
// Kubernetes API or returns an error if any of the defaults are impossible or invalid.
|
|
|
|
func SetKubernetesDefaults(config *Config) error {
|
|
|
|
if config.Prefix == "" {
|
|
|
|
config.Prefix = "/api"
|
|
|
|
}
|
2015-02-04 21:35:53 +00:00
|
|
|
if len(config.UserAgent) == 0 {
|
|
|
|
config.UserAgent = DefaultKubernetesUserAgent()
|
|
|
|
}
|
2015-01-06 17:36:08 +00:00
|
|
|
if len(config.Version) == 0 {
|
|
|
|
config.Version = defaultVersionFor(config)
|
|
|
|
}
|
|
|
|
version := config.Version
|
2015-09-10 19:30:47 +00:00
|
|
|
versionInterfaces, err := latest.GroupOrDie("").InterfacesFor(version)
|
2014-09-30 00:15:00 +00:00
|
|
|
if err != nil {
|
2015-09-10 19:30:47 +00:00
|
|
|
return fmt.Errorf("API version '%s' is not recognized (valid values: %s)", version, strings.Join(latest.GroupOrDie("").Versions, ", "))
|
2015-01-06 17:36:08 +00:00
|
|
|
}
|
|
|
|
if config.Codec == nil {
|
|
|
|
config.Codec = versionInterfaces.Codec
|
|
|
|
}
|
2015-03-31 03:21:21 +00:00
|
|
|
if config.QPS == 0.0 {
|
|
|
|
config.QPS = 5.0
|
|
|
|
}
|
2015-04-09 17:12:52 +00:00
|
|
|
if config.Burst == 0 {
|
|
|
|
config.Burst = 10
|
|
|
|
}
|
2015-01-06 17:36:08 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
|
|
|
|
// object. Note that a RESTClient may require fields that are optional when initializing a Client.
|
|
|
|
// A RESTClient created by this method is generic - it expects to operate on an API that follows
|
|
|
|
// the Kubernetes conventions, but may not be the Kubernetes API.
|
|
|
|
func RESTClientFor(config *Config) (*RESTClient, error) {
|
|
|
|
if len(config.Version) == 0 {
|
|
|
|
return nil, fmt.Errorf("version is required when initializing a RESTClient")
|
|
|
|
}
|
|
|
|
if config.Codec == nil {
|
|
|
|
return nil, fmt.Errorf("Codec is required when initializing a RESTClient")
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
baseURL, err := defaultServerUrlFor(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-06-15 22:15:55 +00:00
|
|
|
client := NewRESTClient(baseURL, config.Version, config.Codec, config.QPS, config.Burst)
|
2014-09-30 00:15:00 +00:00
|
|
|
|
|
|
|
transport, err := TransportFor(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if transport != http.DefaultTransport {
|
|
|
|
client.Client = &http.Client{Transport: transport}
|
|
|
|
}
|
|
|
|
return client, nil
|
|
|
|
}
|
|
|
|
|
2015-07-24 17:55:11 +00:00
|
|
|
var (
|
|
|
|
// tlsTransports stores reusable round trippers with custom TLSClientConfig options
|
|
|
|
tlsTransports = map[string]*http.Transport{}
|
|
|
|
|
|
|
|
// tlsTransportLock protects retrieval and storage of round trippers into the tlsTransports map
|
|
|
|
tlsTransportLock sync.Mutex
|
|
|
|
)
|
|
|
|
|
|
|
|
// tlsTransportFor returns a http.RoundTripper for the given config, or an error
|
|
|
|
// The same RoundTripper will be returned for configs with identical TLS options
|
|
|
|
// If the config has no custom TLS options, http.DefaultTransport is returned
|
|
|
|
func tlsTransportFor(config *Config) (http.RoundTripper, error) {
|
|
|
|
// Get a unique key for the TLS options in the config
|
|
|
|
key, err := tlsConfigKey(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure we only create a single transport for the given TLS options
|
|
|
|
tlsTransportLock.Lock()
|
|
|
|
defer tlsTransportLock.Unlock()
|
|
|
|
|
|
|
|
// See if we already have a custom transport for this config
|
|
|
|
if cachedTransport, ok := tlsTransports[key]; ok {
|
|
|
|
return cachedTransport, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the TLS options for this client config
|
|
|
|
tlsConfig, err := TLSConfigFor(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// The options didn't require a custom TLS config
|
|
|
|
if tlsConfig == nil {
|
|
|
|
return http.DefaultTransport, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cache a single transport for these options
|
|
|
|
tlsTransports[key] = &http.Transport{
|
|
|
|
TLSClientConfig: tlsConfig,
|
|
|
|
Proxy: http.ProxyFromEnvironment,
|
|
|
|
Dial: (&net.Dialer{
|
|
|
|
Timeout: 30 * time.Second,
|
|
|
|
KeepAlive: 30 * time.Second,
|
|
|
|
}).Dial,
|
|
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|
|
|
}
|
|
|
|
return tlsTransports[key], nil
|
|
|
|
}
|
|
|
|
|
2014-09-30 00:15:00 +00:00
|
|
|
// 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) {
|
2015-01-16 16:51:53 +00:00
|
|
|
hasCA := len(config.CAFile) > 0 || len(config.CAData) > 0
|
|
|
|
hasCert := len(config.CertFile) > 0 || len(config.CertData) > 0
|
|
|
|
|
2014-09-30 00:15:00 +00:00
|
|
|
// Set transport level security
|
2015-01-16 16:51:53 +00:00
|
|
|
if config.Transport != nil && (hasCA || hasCert || config.Insecure) {
|
2014-09-30 00:15:00 +00:00
|
|
|
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
|
|
|
|
}
|
2015-01-29 22:43:09 +00:00
|
|
|
|
2015-07-24 17:55:11 +00:00
|
|
|
var (
|
|
|
|
transport http.RoundTripper
|
|
|
|
err error
|
|
|
|
)
|
2015-01-29 22:43:09 +00:00
|
|
|
|
|
|
|
if config.Transport != nil {
|
2014-09-30 00:15:00 +00:00
|
|
|
transport = config.Transport
|
2015-01-29 22:43:09 +00:00
|
|
|
} else {
|
2015-07-24 17:55:11 +00:00
|
|
|
transport, err = tlsTransportFor(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-01-16 16:51:53 +00:00
|
|
|
}
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
2015-06-18 17:00:29 +00:00
|
|
|
|
2015-07-24 17:55:11 +00:00
|
|
|
// Call wrap prior to adding debugging wrappers
|
|
|
|
if config.WrapTransport != nil {
|
|
|
|
transport = config.WrapTransport(transport)
|
|
|
|
}
|
|
|
|
|
2015-06-18 17:00:29 +00:00
|
|
|
switch {
|
|
|
|
case bool(glog.V(9)):
|
|
|
|
transport = NewDebuggingRoundTripper(transport, CurlCommand, URLTiming, ResponseHeaders)
|
|
|
|
case bool(glog.V(8)):
|
|
|
|
transport = NewDebuggingRoundTripper(transport, JustURL, RequestHeaders, ResponseStatus, ResponseHeaders)
|
|
|
|
case bool(glog.V(7)):
|
|
|
|
transport = NewDebuggingRoundTripper(transport, JustURL, RequestHeaders, ResponseStatus)
|
|
|
|
case bool(glog.V(6)):
|
|
|
|
transport = NewDebuggingRoundTripper(transport, URLTiming)
|
|
|
|
}
|
|
|
|
|
2015-01-29 22:43:09 +00:00
|
|
|
transport, err = HTTPWrappersForConfig(config, transport)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: use the config context to wrap a transport
|
|
|
|
|
|
|
|
return transport, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// HTTPWrappersForConfig wraps a round tripper with any relevant layered behavior from the
|
|
|
|
// config. Exposed to allow more clients that need HTTP-like behavior but then must hijack
|
|
|
|
// the underlying connection (like WebSocket or HTTP2 clients). Pure HTTP clients should use
|
|
|
|
// the higher level TransportFor or RESTClientFor methods.
|
|
|
|
func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
|
2014-09-30 00:15:00 +00:00
|
|
|
// 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 != "":
|
2015-01-29 22:43:09 +00:00
|
|
|
rt = NewBearerAuthRoundTripper(config.BearerToken, rt)
|
2014-09-30 00:15:00 +00:00
|
|
|
case hasBasicAuth:
|
2015-01-29 22:43:09 +00:00
|
|
|
rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
2015-02-04 21:35:53 +00:00
|
|
|
if len(config.UserAgent) > 0 {
|
|
|
|
rt = NewUserAgentRoundTripper(config.UserAgent, rt)
|
|
|
|
}
|
2015-01-29 22:43:09 +00:00
|
|
|
return rt, nil
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2014-11-14 05:42:36 +00:00
|
|
|
func DefaultServerURL(host, prefix, version string, defaultTLS bool) (*url.URL, error) {
|
2014-09-30 00:15:00 +00:00
|
|
|
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 == "" {
|
|
|
|
scheme := "http://"
|
2014-11-14 05:42:36 +00:00
|
|
|
if defaultTLS {
|
2014-09-30 00:15:00 +00:00
|
|
|
scheme = "https://"
|
|
|
|
}
|
|
|
|
hostURL, err = url.Parse(scheme + base)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if hostURL.Path != "" && hostURL.Path != "/" {
|
2015-08-27 21:30:31 +00:00
|
|
|
return nil, fmt.Errorf("host must be a URL or a host:port pair: %q", base)
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the user specified a URL without a path component (http://server.com), automatically
|
2014-10-03 02:03:52 +00:00
|
|
|
// append the default prefix
|
2014-09-30 00:15:00 +00:00
|
|
|
if hostURL.Path == "" {
|
2014-10-03 02:03:52 +00:00
|
|
|
if prefix == "" {
|
|
|
|
prefix = "/"
|
|
|
|
}
|
|
|
|
hostURL.Path = prefix
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add the version to the end of the path
|
|
|
|
hostURL.Path = path.Join(hostURL.Path, version)
|
|
|
|
|
|
|
|
return hostURL, nil
|
|
|
|
}
|
|
|
|
|
2015-09-15 02:25:13 +00:00
|
|
|
// IsConfigTransportTLS returns true if and only if the provided config will result in a protected
|
2014-09-30 00:15:00 +00:00
|
|
|
// 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.
|
2015-01-08 15:22:09 +00:00
|
|
|
func IsConfigTransportTLS(config Config) bool {
|
|
|
|
// determination of TLS transport does not logically require a version to be specified
|
|
|
|
// modify the copy of the config we got to satisfy preconditions for defaultServerUrlFor
|
|
|
|
config.Version = defaultVersionFor(&config)
|
|
|
|
|
|
|
|
baseURL, err := defaultServerUrlFor(&config)
|
2014-09-30 00:15:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return baseURL.Scheme == "https"
|
|
|
|
}
|
|
|
|
|
2015-01-06 17:36:08 +00:00
|
|
|
// defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It
|
|
|
|
// requires Host and Version to be set prior to being called.
|
2014-09-30 00:15:00 +00:00
|
|
|
func defaultServerUrlFor(config *Config) (*url.URL, error) {
|
|
|
|
// TODO: move the default to secure when the apiserver supports TLS by default
|
2014-11-14 05:42:36 +00:00
|
|
|
// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
|
2015-02-18 02:37:43 +00:00
|
|
|
hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0
|
|
|
|
hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0
|
|
|
|
defaultTLS := hasCA || hasCert || config.Insecure
|
2014-09-30 00:15:00 +00:00
|
|
|
host := config.Host
|
|
|
|
if host == "" {
|
|
|
|
host = "localhost"
|
|
|
|
}
|
2015-01-06 17:36:08 +00:00
|
|
|
return DefaultServerURL(host, config.Prefix, config.Version, defaultTLS)
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
2015-09-10 19:30:47 +00:00
|
|
|
version = latest.GroupOrDie("").Version
|
2014-09-30 00:15:00 +00:00
|
|
|
}
|
|
|
|
return version
|
|
|
|
}
|
2015-02-04 21:35:53 +00:00
|
|
|
|
|
|
|
// DefaultKubernetesUserAgent returns the default user agent that clients can use.
|
|
|
|
func DefaultKubernetesUserAgent() string {
|
|
|
|
commit := version.Get().GitCommit
|
|
|
|
if len(commit) > 7 {
|
|
|
|
commit = commit[:7]
|
|
|
|
}
|
|
|
|
if len(commit) == 0 {
|
|
|
|
commit = "unknown"
|
|
|
|
}
|
|
|
|
version := version.Get().GitVersion
|
|
|
|
seg := strings.SplitN(version, "-", 2)
|
|
|
|
version = seg[0]
|
|
|
|
return fmt.Sprintf("%s/%s (%s/%s) kubernetes/%s", path.Base(os.Args[0]), version, gruntime.GOOS, gruntime.GOARCH, commit)
|
|
|
|
}
|