github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/k8s.io/kubernetes/pkg/client/unversioned/helper.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package unversioned
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net"
    24  	"net/http"
    25  	"net/url"
    26  	"os"
    27  	"path"
    28  	"reflect"
    29  	gruntime "runtime"
    30  	"strings"
    31  	"time"
    32  
    33  	"github.com/golang/glog"
    34  	"k8s.io/kubernetes/pkg/api"
    35  	"k8s.io/kubernetes/pkg/api/latest"
    36  	"k8s.io/kubernetes/pkg/api/unversioned"
    37  	"k8s.io/kubernetes/pkg/runtime"
    38  	"k8s.io/kubernetes/pkg/util"
    39  	"k8s.io/kubernetes/pkg/util/sets"
    40  	"k8s.io/kubernetes/pkg/version"
    41  )
    42  
    43  // Config holds the common attributes that can be passed to a Kubernetes client on
    44  // initialization.
    45  type Config struct {
    46  	// Host must be a host string, a host:port pair, or a URL to the base of the API.
    47  	Host string
    48  	// Prefix is the sub path of the server. If not specified, the client will set
    49  	// a default value.  Use "/" to indicate the server root should be used
    50  	Prefix string
    51  	// GroupVersion is the API version to talk to. Must be provided when initializing
    52  	// a RESTClient directly. When initializing a Client, will be set with the default
    53  	// code version.
    54  	GroupVersion *unversioned.GroupVersion
    55  	// Codec specifies the encoding and decoding behavior for runtime.Objects passed
    56  	// to a RESTClient or Client. Required when initializing a RESTClient, optional
    57  	// when initializing a Client.
    58  	Codec runtime.Codec
    59  
    60  	// Server requires Basic authentication
    61  	Username string
    62  	Password string
    63  
    64  	// Server requires Bearer authentication. This client will not attempt to use
    65  	// refresh tokens for an OAuth2 flow.
    66  	// TODO: demonstrate an OAuth2 compatible client.
    67  	BearerToken string
    68  
    69  	// TLSClientConfig contains settings to enable transport layer security
    70  	TLSClientConfig
    71  
    72  	// Server should be accessed without verifying the TLS
    73  	// certificate. For testing only.
    74  	Insecure bool
    75  
    76  	// UserAgent is an optional field that specifies the caller of this request.
    77  	UserAgent string
    78  
    79  	// Transport may be used for custom HTTP behavior. This attribute may not
    80  	// be specified with the TLS client certificate options. Use WrapTransport
    81  	// for most client level operations.
    82  	Transport http.RoundTripper
    83  	// WrapTransport will be invoked for custom HTTP behavior after the underlying
    84  	// transport is initialized (either the transport created from TLSClientConfig,
    85  	// Transport, or http.DefaultTransport). The config may layer other RoundTrippers
    86  	// on top of the returned RoundTripper.
    87  	WrapTransport func(rt http.RoundTripper) http.RoundTripper
    88  
    89  	// QPS indicates the maximum QPS to the master from this client.  If zero, QPS is unlimited.
    90  	QPS float32
    91  
    92  	// Maximum burst for throttle
    93  	Burst int
    94  }
    95  
    96  type KubeletConfig struct {
    97  	// ToDo: Add support for different kubelet instances exposing different ports
    98  	Port        uint
    99  	EnableHttps bool
   100  
   101  	// TLSClientConfig contains settings to enable transport layer security
   102  	TLSClientConfig
   103  
   104  	// Server requires Bearer authentication
   105  	BearerToken string
   106  
   107  	// HTTPTimeout is used by the client to timeout http requests to Kubelet.
   108  	HTTPTimeout time.Duration
   109  
   110  	// Dial is a custom dialer used for the client
   111  	Dial func(net, addr string) (net.Conn, error)
   112  }
   113  
   114  // TLSClientConfig contains settings to enable transport layer security
   115  type TLSClientConfig struct {
   116  	// Server requires TLS client certificate authentication
   117  	CertFile string
   118  	// Server requires TLS client certificate authentication
   119  	KeyFile string
   120  	// Trusted root certificates for server
   121  	CAFile string
   122  
   123  	// CertData holds PEM-encoded bytes (typically read from a client certificate file).
   124  	// CertData takes precedence over CertFile
   125  	CertData []byte
   126  	// KeyData holds PEM-encoded bytes (typically read from a client certificate key file).
   127  	// KeyData takes precedence over KeyFile
   128  	KeyData []byte
   129  	// CAData holds PEM-encoded bytes (typically read from a root certificates bundle).
   130  	// CAData takes precedence over CAFile
   131  	CAData []byte
   132  }
   133  
   134  // New creates a Kubernetes client for the given config. This client works with pods,
   135  // replication controllers, daemons, and services. It allows operations such as list, get, update
   136  // and delete on these objects. An error is returned if the provided configuration
   137  // is not valid.
   138  func New(c *Config) (*Client, error) {
   139  	config := *c
   140  	if err := SetKubernetesDefaults(&config); err != nil {
   141  		return nil, err
   142  	}
   143  	client, err := RESTClientFor(&config)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	discoveryConfig := *c
   149  	discoveryClient, err := NewDiscoveryClient(&discoveryConfig)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	if _, err := latest.Group("extensions"); err != nil {
   155  		return &Client{RESTClient: client, ExtensionsClient: nil, DiscoveryClient: discoveryClient}, nil
   156  	}
   157  	experimentalConfig := *c
   158  	experimentalClient, err := NewExtensions(&experimentalConfig)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	return &Client{RESTClient: client, ExtensionsClient: experimentalClient, DiscoveryClient: discoveryClient}, nil
   164  }
   165  
   166  // MatchesServerVersion queries the server to compares the build version
   167  // (git hash) of the client with the server's build version. It returns an error
   168  // if it failed to contact the server or if the versions are not an exact match.
   169  func MatchesServerVersion(client *Client, c *Config) error {
   170  	var err error
   171  	if client == nil {
   172  		client, err = New(c)
   173  		if err != nil {
   174  			return err
   175  		}
   176  	}
   177  	clientVersion := version.Get()
   178  	serverVersion, err := client.ServerVersion()
   179  	if err != nil {
   180  		return fmt.Errorf("couldn't read version from server: %v\n", err)
   181  	}
   182  	if s := *serverVersion; !reflect.DeepEqual(clientVersion, s) {
   183  		return fmt.Errorf("server version (%#v) differs from client version (%#v)!\n", s, clientVersion)
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func ExtractGroupVersions(l *unversioned.APIGroupList) []string {
   190  	var groupVersions []string
   191  	for _, g := range l.Groups {
   192  		for _, gv := range g.Versions {
   193  			groupVersions = append(groupVersions, gv.GroupVersion)
   194  		}
   195  	}
   196  	return groupVersions
   197  }
   198  
   199  // ServerAPIVersions returns the GroupVersions supported by the API server.
   200  // It creates a RESTClient based on the passed in config, but it doesn't rely
   201  // on the Version, Codec, and Prefix of the config, because it uses AbsPath and
   202  // takes the raw response.
   203  func ServerAPIVersions(c *Config) (groupVersions []string, err error) {
   204  	transport, err := TransportFor(c)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	client := http.Client{Transport: transport}
   209  
   210  	configCopy := *c
   211  	configCopy.GroupVersion = nil
   212  	configCopy.Prefix = ""
   213  	baseURL, err := defaultServerUrlFor(c)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	// Get the groupVersions exposed at /api
   218  	baseURL.Path = "/api"
   219  	resp, err := client.Get(baseURL.String())
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	var v unversioned.APIVersions
   224  	defer resp.Body.Close()
   225  	err = json.NewDecoder(resp.Body).Decode(&v)
   226  	if err != nil {
   227  		return nil, fmt.Errorf("unexpected error: %v", err)
   228  	}
   229  
   230  	groupVersions = append(groupVersions, v.Versions...)
   231  	// Get the groupVersions exposed at /apis
   232  	baseURL.Path = "/apis"
   233  	resp2, err := client.Get(baseURL.String())
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  	var apiGroupList unversioned.APIGroupList
   238  	defer resp2.Body.Close()
   239  	err = json.NewDecoder(resp2.Body).Decode(&apiGroupList)
   240  	if err != nil {
   241  		return nil, fmt.Errorf("unexpected error: %v", err)
   242  	}
   243  	groupVersions = append(groupVersions, ExtractGroupVersions(&apiGroupList)...)
   244  
   245  	return groupVersions, nil
   246  }
   247  
   248  // NegotiateVersion queries the server's supported api versions to find
   249  // a version that both client and server support.
   250  // - If no version is provided, try registered client versions in order of
   251  //   preference.
   252  // - If version is provided, but not default config (explicitly requested via
   253  //   commandline flag), and is unsupported by the server, print a warning to
   254  //   stderr and try client's registered versions in order of preference.
   255  // - If version is config default, and the server does not support it,
   256  //   return an error.
   257  func NegotiateVersion(client *Client, c *Config, requestedGV *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) {
   258  	var err error
   259  	if client == nil {
   260  		client, err = New(c)
   261  		if err != nil {
   262  			return nil, err
   263  		}
   264  	}
   265  	clientVersions := sets.String{}
   266  	for _, gv := range clientRegisteredGVs {
   267  		clientVersions.Insert(gv.String())
   268  	}
   269  	apiVersions, err := client.ServerAPIVersions()
   270  	if err != nil {
   271  		// This is almost always a connection error, and higher level code should treat this as a generic error,
   272  		// not a negotiation specific error.
   273  		return nil, err
   274  	}
   275  	serverVersions := sets.String{}
   276  	for _, v := range apiVersions.Versions {
   277  		serverVersions.Insert(v)
   278  	}
   279  
   280  	// If no version requested, use config version (may also be empty).
   281  	// make a copy of the original so we don't risk mutating input here or in the returned value
   282  	var preferredGV *unversioned.GroupVersion
   283  	switch {
   284  	case requestedGV != nil:
   285  		t := *requestedGV
   286  		preferredGV = &t
   287  	case c.GroupVersion != nil:
   288  		t := *c.GroupVersion
   289  		preferredGV = &t
   290  	}
   291  
   292  	// If version explicitly requested verify that both client and server support it.
   293  	// If server does not support warn, but try to negotiate a lower version.
   294  	if preferredGV != nil {
   295  		if !clientVersions.Has(preferredGV.String()) {
   296  			return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", preferredGV, clientVersions)
   297  
   298  		}
   299  		if serverVersions.Has(preferredGV.String()) {
   300  			return preferredGV, nil
   301  		}
   302  		// If we are using an explicit config version the server does not support, fail.
   303  		if (c.GroupVersion != nil) && (*preferredGV == *c.GroupVersion) {
   304  			return nil, fmt.Errorf("server does not support API version %q", preferredGV)
   305  		}
   306  	}
   307  
   308  	for _, clientGV := range clientRegisteredGVs {
   309  		if serverVersions.Has(clientGV.String()) {
   310  			// Version was not explicitly requested in command config (--api-version).
   311  			// Ok to fall back to a supported version with a warning.
   312  			// TODO: caesarxuchao: enable the warning message when we have
   313  			// proper fix. Please refer to issue #14895.
   314  			// if len(version) != 0 {
   315  			// 	glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion)
   316  			// }
   317  			t := clientGV
   318  			return &t, nil
   319  		}
   320  	}
   321  	return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v",
   322  		serverVersions, clientVersions)
   323  }
   324  
   325  // NewOrDie creates a Kubernetes client and panics if the provided API version is not recognized.
   326  func NewOrDie(c *Config) *Client {
   327  	client, err := New(c)
   328  	if err != nil {
   329  		panic(err)
   330  	}
   331  	return client
   332  }
   333  
   334  // InClusterConfig returns a config object which uses the service account
   335  // kubernetes gives to pods. It's intended for clients that expect to be
   336  // running inside a pod running on kuberenetes. It will return an error if
   337  // called from a process not running in a kubernetes environment.
   338  func InClusterConfig() (*Config, error) {
   339  	host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
   340  	if len(host) == 0 || len(port) == 0 {
   341  		return nil, fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined")
   342  	}
   343  
   344  	token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountTokenKey)
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  	tlsClientConfig := TLSClientConfig{}
   349  	rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountRootCAKey
   350  	if _, err := util.CertPoolFromFile(rootCAFile); err != nil {
   351  		glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
   352  	} else {
   353  		tlsClientConfig.CAFile = rootCAFile
   354  	}
   355  
   356  	return &Config{
   357  		// TODO: switch to using cluster DNS.
   358  		Host:            "https://" + net.JoinHostPort(host, port),
   359  		BearerToken:     string(token),
   360  		TLSClientConfig: tlsClientConfig,
   361  	}, nil
   362  }
   363  
   364  // NewInCluster is a shortcut for calling InClusterConfig() and then New().
   365  func NewInCluster() (*Client, error) {
   366  	cc, err := InClusterConfig()
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  	return New(cc)
   371  }
   372  
   373  // SetKubernetesDefaults sets default values on the provided client config for accessing the
   374  // Kubernetes API or returns an error if any of the defaults are impossible or invalid.
   375  // TODO: this method needs to be split into one that sets defaults per group, expected to be fix in PR "Refactoring clientcache.go and helper.go #14592"
   376  func SetKubernetesDefaults(config *Config) error {
   377  	if config.Prefix == "" {
   378  		config.Prefix = "/api"
   379  	}
   380  	if len(config.UserAgent) == 0 {
   381  		config.UserAgent = DefaultKubernetesUserAgent()
   382  	}
   383  	if config.GroupVersion == nil {
   384  		config.GroupVersion = defaultVersionFor(config)
   385  	}
   386  	versionInterfaces, err := latest.GroupOrDie("").InterfacesFor(config.GroupVersion.String())
   387  	if err != nil {
   388  		return fmt.Errorf("API version '%v' is not recognized (valid values: %s)", *config.GroupVersion, strings.Join(latest.GroupOrDie("").Versions, ", "))
   389  	}
   390  	if config.Codec == nil {
   391  		config.Codec = versionInterfaces.Codec
   392  	}
   393  	if config.QPS == 0.0 {
   394  		config.QPS = 5.0
   395  	}
   396  	if config.Burst == 0 {
   397  		config.Burst = 10
   398  	}
   399  	return nil
   400  }
   401  
   402  // RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
   403  // object. Note that a RESTClient may require fields that are optional when initializing a Client.
   404  // A RESTClient created by this method is generic - it expects to operate on an API that follows
   405  // the Kubernetes conventions, but may not be the Kubernetes API.
   406  func RESTClientFor(config *Config) (*RESTClient, error) {
   407  	if config.GroupVersion == nil {
   408  		return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")
   409  	}
   410  	if config.Codec == nil {
   411  		return nil, fmt.Errorf("Codec is required when initializing a RESTClient")
   412  	}
   413  
   414  	baseURL, err := defaultServerUrlFor(config)
   415  	if err != nil {
   416  		return nil, err
   417  	}
   418  
   419  	client := NewRESTClient(baseURL, config.GroupVersion.String(), config.Codec, config.QPS, config.Burst)
   420  
   421  	transport, err := TransportFor(config)
   422  	if err != nil {
   423  		return nil, err
   424  	}
   425  
   426  	if transport != http.DefaultTransport {
   427  		client.Client = &http.Client{Transport: transport}
   428  	}
   429  	return client, nil
   430  }
   431  
   432  // UnversionedRESTClientFor is the same as RESTClientFor, except that it allows
   433  // the config.Version to be empty.
   434  func UnversionedRESTClientFor(config *Config) (*RESTClient, error) {
   435  	if config.Codec == nil {
   436  		return nil, fmt.Errorf("Codec is required when initializing a RESTClient")
   437  	}
   438  
   439  	baseURL, err := defaultServerUrlFor(config)
   440  	if err != nil {
   441  		return nil, err
   442  	}
   443  
   444  	client := NewRESTClient(baseURL, "", config.Codec, config.QPS, config.Burst)
   445  
   446  	transport, err := TransportFor(config)
   447  	if err != nil {
   448  		return nil, err
   449  	}
   450  
   451  	if transport != http.DefaultTransport {
   452  		client.Client = &http.Client{Transport: transport}
   453  	}
   454  	return client, nil
   455  }
   456  
   457  // DefaultServerURL converts a host, host:port, or URL string to the default base server API path
   458  // to use with a Client at a given API version following the standard conventions for a
   459  // Kubernetes API.
   460  func DefaultServerURL(host, prefix string, groupVersion unversioned.GroupVersion, defaultTLS bool) (*url.URL, error) {
   461  	if host == "" {
   462  		return nil, fmt.Errorf("host must be a URL or a host:port pair")
   463  	}
   464  	base := host
   465  	hostURL, err := url.Parse(base)
   466  	if err != nil {
   467  		return nil, err
   468  	}
   469  	if hostURL.Scheme == "" {
   470  		scheme := "http://"
   471  		if defaultTLS {
   472  			scheme = "https://"
   473  		}
   474  		hostURL, err = url.Parse(scheme + base)
   475  		if err != nil {
   476  			return nil, err
   477  		}
   478  		if hostURL.Path != "" && hostURL.Path != "/" {
   479  			return nil, fmt.Errorf("host must be a URL or a host:port pair: %q", base)
   480  		}
   481  	}
   482  
   483  	// If the user specified a URL without a path component (http://server.com), automatically
   484  	// append the default prefix
   485  	if hostURL.Path == "" {
   486  		if prefix == "" {
   487  			prefix = "/"
   488  		}
   489  		hostURL.Path = prefix
   490  	}
   491  
   492  	// Add the version to the end of the path
   493  	if len(groupVersion.Group) > 0 {
   494  		hostURL.Path = path.Join(hostURL.Path, groupVersion.Group, groupVersion.Version)
   495  
   496  	} else {
   497  		hostURL.Path = path.Join(hostURL.Path, groupVersion.Version)
   498  
   499  	}
   500  
   501  	return hostURL, nil
   502  }
   503  
   504  // IsConfigTransportTLS returns true if and only if the provided config will result in a protected
   505  // connection to the server when it is passed to client.New() or client.RESTClientFor().
   506  // Use to determine when to send credentials over the wire.
   507  //
   508  // Note: the Insecure flag is ignored when testing for this value, so MITM attacks are
   509  // still possible.
   510  func IsConfigTransportTLS(config Config) bool {
   511  	// determination of TLS transport does not logically require a version to be specified
   512  	// modify the copy of the config we got to satisfy preconditions for defaultServerUrlFor
   513  	config.GroupVersion = defaultVersionFor(&config)
   514  
   515  	baseURL, err := defaultServerUrlFor(&config)
   516  	if err != nil {
   517  		return false
   518  	}
   519  	return baseURL.Scheme == "https"
   520  }
   521  
   522  // defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It
   523  // requires Host and Version to be set prior to being called.
   524  func defaultServerUrlFor(config *Config) (*url.URL, error) {
   525  	// TODO: move the default to secure when the apiserver supports TLS by default
   526  	// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
   527  	hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0
   528  	hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0
   529  	defaultTLS := hasCA || hasCert || config.Insecure
   530  	host := config.Host
   531  	if host == "" {
   532  		host = "localhost"
   533  	}
   534  
   535  	if config.GroupVersion != nil {
   536  		return DefaultServerURL(host, config.Prefix, *config.GroupVersion, defaultTLS)
   537  	}
   538  	return DefaultServerURL(host, config.Prefix, unversioned.GroupVersion{}, defaultTLS)
   539  }
   540  
   541  // defaultVersionFor is shared between defaultServerUrlFor and RESTClientFor
   542  func defaultVersionFor(config *Config) *unversioned.GroupVersion {
   543  	if config.GroupVersion == nil {
   544  		// Clients default to the preferred code API version
   545  		// TODO: implement version negotiation (highest version supported by server)
   546  		// TODO this drops out when groupmeta is refactored
   547  		gv, err := unversioned.ParseGroupVersion(latest.GroupOrDie("").GroupVersion)
   548  		if err != nil {
   549  			panic(err)
   550  		}
   551  		return &gv
   552  	}
   553  
   554  	return config.GroupVersion
   555  }
   556  
   557  // DefaultKubernetesUserAgent returns the default user agent that clients can use.
   558  func DefaultKubernetesUserAgent() string {
   559  	commit := version.Get().GitCommit
   560  	if len(commit) > 7 {
   561  		commit = commit[:7]
   562  	}
   563  	if len(commit) == 0 {
   564  		commit = "unknown"
   565  	}
   566  	version := version.Get().GitVersion
   567  	seg := strings.SplitN(version, "-", 2)
   568  	version = seg[0]
   569  	return fmt.Sprintf("%s/%s (%s/%s) kubernetes/%s", path.Base(os.Args[0]), version, gruntime.GOOS, gruntime.GOARCH, commit)
   570  }
   571  
   572  // TimeoutFromListOptions returns timeout to be set via TimeoutSeconds() method
   573  // based on given options.
   574  func TimeoutFromListOptions(options api.ListOptions) time.Duration {
   575  	if options.TimeoutSeconds != nil {
   576  		return time.Duration(*options.TimeoutSeconds) * time.Second
   577  	}
   578  	return 0
   579  }