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 }