2014-06-22 19:05:34 +00:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2014-06-22 21:18:01 +00:00
|
|
|
package client
|
2014-06-22 19:05:34 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2014-07-17 23:09:29 +00:00
|
|
|
"fmt"
|
2014-06-22 19:05:34 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"path"
|
2014-08-05 22:23:33 +00:00
|
|
|
"strconv"
|
2014-06-22 19:05:34 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
2014-09-02 17:55:27 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
2014-08-08 20:50:04 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
2014-07-17 23:09:29 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
2014-09-17 04:33:48 +00:00
|
|
|
watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
|
2014-07-09 05:35:58 +00:00
|
|
|
"github.com/golang/glog"
|
2014-06-22 19:05:34 +00:00
|
|
|
)
|
|
|
|
|
2014-08-08 20:50:04 +00:00
|
|
|
// specialParams lists parameters that are handled specially and which users of Request
|
|
|
|
// are therefore not allowed to set manually.
|
|
|
|
var specialParams = util.NewStringSet("sync", "timeout")
|
|
|
|
|
2014-06-22 19:05:34 +00:00
|
|
|
// Request allows for building up a request to a server in a chained fashion.
|
2014-06-23 00:02:48 +00:00
|
|
|
// Any errors are stored until the end of your call, so you only have to
|
|
|
|
// check once.
|
2014-06-22 19:05:34 +00:00
|
|
|
type Request struct {
|
2014-08-21 21:14:06 +00:00
|
|
|
c *RESTClient
|
2014-06-26 23:10:38 +00:00
|
|
|
err error
|
|
|
|
verb string
|
|
|
|
path string
|
|
|
|
body io.Reader
|
2014-08-05 22:23:33 +00:00
|
|
|
params map[string]string
|
2014-06-26 23:10:38 +00:00
|
|
|
selector labels.Selector
|
|
|
|
timeout time.Duration
|
|
|
|
sync bool
|
|
|
|
pollPeriod time.Duration
|
2014-06-22 19:05:34 +00:00
|
|
|
}
|
|
|
|
|
2014-07-08 07:15:41 +00:00
|
|
|
// Path appends an item to the request path. You must call Path at least once.
|
2014-06-22 19:05:34 +00:00
|
|
|
func (r *Request) Path(item string) *Request {
|
|
|
|
if r.err != nil {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
r.path = path.Join(r.path, item)
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// Sync sets sync/async call status by setting the "sync" parameter to "true"/"false".
|
2014-06-25 23:23:15 +00:00
|
|
|
func (r *Request) Sync(sync bool) *Request {
|
|
|
|
if r.err != nil {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
r.sync = sync
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2014-07-08 07:15:41 +00:00
|
|
|
// AbsPath overwrites an existing path with the path parameter.
|
2014-06-24 21:57:09 +00:00
|
|
|
func (r *Request) AbsPath(path string) *Request {
|
|
|
|
if r.err != nil {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
r.path = path
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2014-08-05 22:23:33 +00:00
|
|
|
// ParseSelectorParam parses the given string as a resource label selector.
|
|
|
|
// This is a convenience function so you don't have to first check that it's a
|
|
|
|
// validly formatted selector.
|
|
|
|
func (r *Request) ParseSelectorParam(paramName, item string) *Request {
|
|
|
|
if r.err != nil {
|
|
|
|
return r
|
|
|
|
}
|
2014-08-08 20:50:04 +00:00
|
|
|
sel, err := labels.ParseSelector(item)
|
|
|
|
if err != nil {
|
2014-08-05 22:23:33 +00:00
|
|
|
r.err = err
|
2014-08-08 20:50:04 +00:00
|
|
|
return r
|
2014-08-05 22:23:33 +00:00
|
|
|
}
|
2014-08-08 20:50:04 +00:00
|
|
|
return r.setParam(paramName, sel.String())
|
2014-08-05 22:23:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SelectorParam adds the given selector as a query parameter with the name paramName.
|
|
|
|
func (r *Request) SelectorParam(paramName string, s labels.Selector) *Request {
|
2014-06-22 19:05:34 +00:00
|
|
|
if r.err != nil {
|
|
|
|
return r
|
|
|
|
}
|
2014-08-08 20:50:04 +00:00
|
|
|
return r.setParam(paramName, s.String())
|
2014-06-22 19:05:34 +00:00
|
|
|
}
|
|
|
|
|
2014-08-05 22:23:33 +00:00
|
|
|
// UintParam creates a query parameter with the given value.
|
|
|
|
func (r *Request) UintParam(paramName string, u uint64) *Request {
|
2014-06-23 00:02:48 +00:00
|
|
|
if r.err != nil {
|
|
|
|
return r
|
|
|
|
}
|
2014-08-08 20:50:04 +00:00
|
|
|
return r.setParam(paramName, strconv.FormatUint(u, 10))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Request) setParam(paramName, value string) *Request {
|
|
|
|
if specialParams.Has(paramName) {
|
|
|
|
r.err = fmt.Errorf("must set %v through the corresponding function, not directly.", paramName)
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
r.params[paramName] = value
|
2014-06-23 00:02:48 +00:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2014-08-05 22:23:33 +00:00
|
|
|
// Timeout makes the request use the given duration as a timeout. Sets the "timeout"
|
|
|
|
// parameter. Ignored if sync=false.
|
2014-06-22 19:05:34 +00:00
|
|
|
func (r *Request) Timeout(d time.Duration) *Request {
|
|
|
|
if r.err != nil {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
r.timeout = d
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2014-07-08 07:15:41 +00:00
|
|
|
// Body makes the request use obj as the body. Optional.
|
2014-06-22 19:05:34 +00:00
|
|
|
// If obj is a string, try to read a file of that name.
|
|
|
|
// If obj is a []byte, send it directly.
|
2014-08-05 22:23:33 +00:00
|
|
|
// If obj is an io.Reader, use it directly.
|
2014-09-06 02:22:03 +00:00
|
|
|
// If obj is a runtime.Object, marshal it correctly.
|
|
|
|
// Otherwise, set an error.
|
2014-06-22 19:05:34 +00:00
|
|
|
func (r *Request) Body(obj interface{}) *Request {
|
|
|
|
if r.err != nil {
|
|
|
|
return r
|
|
|
|
}
|
2014-06-23 00:02:48 +00:00
|
|
|
switch t := obj.(type) {
|
|
|
|
case string:
|
2014-08-08 20:50:04 +00:00
|
|
|
data, err := ioutil.ReadFile(t)
|
|
|
|
if err != nil {
|
2014-06-23 00:02:48 +00:00
|
|
|
r.err = err
|
2014-08-08 20:50:04 +00:00
|
|
|
return r
|
2014-06-23 00:02:48 +00:00
|
|
|
}
|
2014-08-08 20:50:04 +00:00
|
|
|
r.body = bytes.NewBuffer(data)
|
2014-06-23 00:02:48 +00:00
|
|
|
case []byte:
|
|
|
|
r.body = bytes.NewBuffer(t)
|
2014-06-24 21:57:09 +00:00
|
|
|
case io.Reader:
|
2014-08-05 22:23:33 +00:00
|
|
|
r.body = t
|
2014-09-06 02:22:03 +00:00
|
|
|
case runtime.Object:
|
2014-08-29 17:53:14 +00:00
|
|
|
data, err := r.c.Codec.Encode(t)
|
2014-08-08 20:50:04 +00:00
|
|
|
if err != nil {
|
2014-06-23 00:02:48 +00:00
|
|
|
r.err = err
|
2014-08-08 20:50:04 +00:00
|
|
|
return r
|
2014-06-23 00:02:48 +00:00
|
|
|
}
|
2014-08-08 20:50:04 +00:00
|
|
|
r.body = bytes.NewBuffer(data)
|
2014-09-06 02:22:03 +00:00
|
|
|
default:
|
|
|
|
r.err = fmt.Errorf("Unknown type used for body: %#v", obj)
|
2014-06-23 00:02:48 +00:00
|
|
|
}
|
2014-06-22 19:05:34 +00:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2014-06-26 23:10:38 +00:00
|
|
|
// PollPeriod sets the poll period.
|
|
|
|
// If the server sends back a "working" status message, then repeatedly poll the server
|
|
|
|
// to see if the operation has completed yet, waiting 'd' between each poll.
|
|
|
|
// If you want to handle the "working" status yourself (it'll be delivered as StatusErr),
|
|
|
|
// set d to 0 to turn off this behavior.
|
|
|
|
func (r *Request) PollPeriod(d time.Duration) *Request {
|
2014-06-22 19:05:34 +00:00
|
|
|
if r.err != nil {
|
2014-06-26 23:10:38 +00:00
|
|
|
return r
|
2014-06-22 19:05:34 +00:00
|
|
|
}
|
2014-06-26 23:10:38 +00:00
|
|
|
r.pollPeriod = d
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2014-07-17 23:09:29 +00:00
|
|
|
func (r *Request) finalURL() string {
|
2014-09-30 00:15:00 +00:00
|
|
|
finalURL := *r.c.baseURL
|
|
|
|
finalURL.Path = r.path
|
2014-07-17 23:09:29 +00:00
|
|
|
query := url.Values{}
|
2014-08-05 22:23:33 +00:00
|
|
|
for key, value := range r.params {
|
|
|
|
query.Add(key, value)
|
2014-07-17 23:09:29 +00:00
|
|
|
}
|
2014-08-05 22:23:33 +00:00
|
|
|
// sync and timeout are handled specially here, to allow setting them
|
|
|
|
// in any order.
|
2014-07-17 23:09:29 +00:00
|
|
|
if r.sync {
|
|
|
|
query.Add("sync", "true")
|
|
|
|
if r.timeout != 0 {
|
|
|
|
query.Add("timeout", r.timeout.String())
|
|
|
|
}
|
|
|
|
}
|
2014-09-30 00:15:00 +00:00
|
|
|
finalURL.RawQuery = query.Encode()
|
|
|
|
return finalURL.String()
|
2014-07-17 23:09:29 +00:00
|
|
|
}
|
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// Watch attempts to begin watching the requested location.
|
|
|
|
// Returns a watch.Interface, or an error.
|
2014-07-17 23:09:29 +00:00
|
|
|
func (r *Request) Watch() (watch.Interface, error) {
|
|
|
|
if r.err != nil {
|
|
|
|
return nil, r.err
|
|
|
|
}
|
|
|
|
req, err := http.NewRequest(r.verb, r.finalURL(), r.body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-09-30 00:15:00 +00:00
|
|
|
client := r.c.Client
|
|
|
|
if client == nil {
|
|
|
|
client = http.DefaultClient
|
2014-07-17 23:09:29 +00:00
|
|
|
}
|
2014-09-30 00:15:00 +00:00
|
|
|
response, err := client.Do(req)
|
2014-07-17 23:09:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if response.StatusCode != http.StatusOK {
|
|
|
|
return nil, fmt.Errorf("Got status: %v", response.StatusCode)
|
|
|
|
}
|
2014-09-17 04:33:48 +00:00
|
|
|
return watch.NewStreamWatcher(watchjson.NewDecoder(response.Body, r.c.Codec)), nil
|
2014-07-17 23:09:29 +00:00
|
|
|
}
|
|
|
|
|
2014-07-08 07:15:41 +00:00
|
|
|
// Do formats and executes the request. Returns the API object received, or an error.
|
2014-06-26 23:10:38 +00:00
|
|
|
func (r *Request) Do() Result {
|
|
|
|
for {
|
|
|
|
if r.err != nil {
|
|
|
|
return Result{err: r.err}
|
2014-06-25 23:23:15 +00:00
|
|
|
}
|
2014-07-17 23:09:29 +00:00
|
|
|
req, err := http.NewRequest(r.verb, r.finalURL(), r.body)
|
2014-06-26 23:10:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return Result{err: err}
|
|
|
|
}
|
|
|
|
respBody, err := r.c.doRequest(req)
|
|
|
|
if err != nil {
|
2014-09-30 00:15:00 +00:00
|
|
|
if s, ok := err.(APIStatus); ok {
|
|
|
|
status := s.Status()
|
|
|
|
if status.Status == api.StatusWorking && r.pollPeriod != 0 {
|
|
|
|
if status.Details != nil {
|
|
|
|
id := status.Details.ID
|
Evolve the api.Status object with Reason/Details
Contains breaking API change on api.Status#Details (type change)
Turn Details from string -> StatusDetails - a general
bucket for keyed error behavior. Define an open enumeration
ReasonType exposed as Reason on the status object to provide
machine readable subcategorization beyond HTTP Status Code. Define
a human readable field Message which is common convention (previously
this was joined into Details).
Precedence order: HTTP Status Code, Reason, Details. apiserver would
impose restraints on the ReasonTypes defined by the main apiobject,
and ensure their use is consistent.
There are four long term scenarios this change supports:
1. Allow a client access to a machine readable field that can be
easily switched on for improving or translating the generic
server Message.
2. Return a 404 when a composite operation on multiple resources
fails with enough data so that a client can distinguish which
item does not exist. E.g. resource Parent and resource Child,
POST /parents/1/children to create a new Child, but /parents/1
is deleted. POST returns 404, ReasonTypeNotFound, and
Details.ID = "1", Details.Kind = "parent"
3. Allow a client to receive validation data that is keyed by
attribute for building user facing UIs around field submission.
Validation is usually expressed as map[string][]string, but
that type is less appropriate for many other uses.
4. Allow specific API errors to return more granular failure status
for specific operations. An example might be a minion proxy,
where the operation that failed may be both proxying OR the
minion itself. In this case a reason may be defined "proxy_failed"
corresponding to 502, where the Details field may be extended
to contain a nested error object.
At this time only ID and Kind are exposed
2014-07-31 18:12:26 +00:00
|
|
|
if len(id) > 0 {
|
|
|
|
glog.Infof("Waiting for completion of /operations/%s", id)
|
|
|
|
time.Sleep(r.pollPeriod)
|
|
|
|
// Make a poll request
|
|
|
|
pollOp := r.c.PollFor(id).PollPeriod(r.pollPeriod)
|
|
|
|
// Could also say "return r.Do()" but this way doesn't grow the callstack.
|
|
|
|
r = pollOp
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2014-06-26 23:10:38 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-23 01:14:32 +00:00
|
|
|
}
|
2014-08-29 17:53:14 +00:00
|
|
|
return Result{respBody, err, r.c.Codec}
|
2014-06-23 01:14:32 +00:00
|
|
|
}
|
2014-06-23 00:02:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Result contains the result of calling Request.Do().
|
|
|
|
type Result struct {
|
2014-08-29 17:53:14 +00:00
|
|
|
body []byte
|
|
|
|
err error
|
|
|
|
codec runtime.Codec
|
2014-06-23 00:02:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Raw returns the raw result.
|
|
|
|
func (r Result) Raw() ([]byte, error) {
|
|
|
|
return r.body, r.err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get returns the result as an object.
|
2014-09-06 02:22:03 +00:00
|
|
|
func (r Result) Get() (runtime.Object, error) {
|
2014-06-23 00:02:48 +00:00
|
|
|
if r.err != nil {
|
|
|
|
return nil, r.err
|
|
|
|
}
|
2014-08-29 17:53:14 +00:00
|
|
|
return r.codec.Decode(r.body)
|
2014-06-23 00:02:48 +00:00
|
|
|
}
|
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// Into stores the result into obj, if possible.
|
2014-09-06 02:22:03 +00:00
|
|
|
func (r Result) Into(obj runtime.Object) error {
|
2014-06-23 00:02:48 +00:00
|
|
|
if r.err != nil {
|
|
|
|
return r.err
|
2014-06-22 19:05:34 +00:00
|
|
|
}
|
2014-08-29 17:53:14 +00:00
|
|
|
return r.codec.DecodeInto(r.body, obj)
|
2014-06-23 00:02:48 +00:00
|
|
|
}
|
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// Error returns the error executing the request, nil if no error occurred.
|
2014-06-23 00:02:48 +00:00
|
|
|
func (r Result) Error() error {
|
|
|
|
return r.err
|
2014-06-22 19:05:34 +00:00
|
|
|
}
|