Introduce Resource/ResourceName/Prefix/Suffix options to RESTClient

RESTClient is an abstraction for simplifying access to resources that
follow the Kubernetes API pattern.  Currently, both Namespace and Path
are coupled, which means changes across versions is complex.  In general,
most access to resources should be to a resource collection (e.g.
"services") with a name (e.g. "foo"). Other constructs, like prefix sections
("watch") or proposed suffix sections ("/pods/foo/spec") only modify this
core pattern.

This commit removes the Path() helper from Request and introduces:

* Prefix(segments ...string) - segments that should go to the beginning of the path.
* Suffix(segments ...string) - segments that should go to the end of the path.
* Resource(string) - collection name, should be after prefix
* Namespace(string) - if specified, should be set after resource but before name
* Name(string) - if specified, should be after namespace

Now, only Prefix and Suffix are order dependent (and with variadics, should be
simpler).  Resource, Namespace, and Name may be specified in any order.

Path() has been removed to prevent downstream consumers of RESTClient from experiencing
behavior change.
This commit is contained in:
Clayton Coleman 2014-12-23 16:14:32 -05:00
parent 998061677b
commit afedbba3fc

View File

@ -89,16 +89,22 @@ type Request struct {
// whether to poll.
poller PollFunc
// accessible via method setters
path string
params map[string]string
selector labels.Selector
sync bool
timeout time.Duration
// If true, put ns/<namespace> in path; if false, add "?namespace=<namespace>" as a query parameter
namespaceInPath bool
// generic components accessible via method setters
path string
subpath string
params map[string]string
// structural elements of the request that are part of the Kubernetes API conventions
namespace string
resource string
resourceName string
selector labels.Selector
sync bool
timeout time.Duration
// output
err error
body io.Reader
@ -116,12 +122,50 @@ func NewRequest(client HTTPClient, verb string, baseURL *url.URL, codec runtime.
}
}
// Path appends an item to the request path. You must call Path at least once.
func (r *Request) Path(item string) *Request {
// Prefix adds segments to the relative beginning to the request path. These
// items will be placed before the optional Namespace, Resource, or Name sections.
// Setting AbsPath will clear any previously set Prefix segments
func (r *Request) Prefix(segments ...string) *Request {
if r.err != nil {
return r
}
r.path = path.Join(r.path, item)
r.path = path.Join(r.path, path.Join(segments...))
return r
}
// Suffix appends segments to the end of the path. These items will be placed after the prefix and optional
// Namespace, Resource, or Name sections.
func (r *Request) Suffix(segments ...string) *Request {
if r.err != nil {
return r
}
r.subpath = path.Join(r.subpath, path.Join(segments...))
return r
}
// Resource sets the resource to access (<resource>/[ns/<namespace>/]<name>)
func (r *Request) Resource(resource string) *Request {
if r.err != nil {
return r
}
if len(r.resource) != 0 {
r.err = fmt.Errorf("resource already set to %q, cannot change to %q", r.resource, resource)
return r
}
r.resource = resource
return r
}
// Name sets the name of a resource to access (<resource>/[ns/<namespace>/]<name>)
func (r *Request) Name(resourceName string) *Request {
if r.err != nil {
return r
}
if len(r.resourceName) != 0 {
r.err = fmt.Errorf("resource name already set to %q, cannot change to %q", r.resourceName, resourceName)
return r
}
r.resourceName = resourceName
return r
}
@ -134,29 +178,31 @@ func (r *Request) Sync(sync bool) *Request {
return r
}
// Namespace applies the namespace scope to a request
// Namespace applies the namespace scope to a request (<resource>/[ns/<namespace>/]<name>)
func (r *Request) Namespace(namespace string) *Request {
if r.err != nil {
return r
}
if len(namespace) > 0 {
if r.namespaceInPath {
return r.Path("ns").Path(namespace)
} else {
return r.setParam("namespace", namespace)
}
if len(r.namespace) != 0 {
r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace)
return r
}
r.namespace = namespace
return r
}
// AbsPath overwrites an existing path with the path parameter.
func (r *Request) AbsPath(path string) *Request {
// AbsPath overwrites an existing path with the segments provided. Trailing slashes are preserved
// when a single segment is passed.
func (r *Request) AbsPath(segments ...string) *Request {
if r.err != nil {
return r
}
r.path = path
if len(segments) == 1 {
// preserve any trailing slashes for legacy behavior
r.path = segments[0]
} else {
r.path = path.Join(segments...)
}
return r
}
@ -273,13 +319,30 @@ func (r *Request) Poller(poller PollFunc) *Request {
}
func (r *Request) finalURL() string {
p := r.path
if r.namespaceInPath {
p = path.Join(p, "ns", r.namespace)
}
if len(r.resource) != 0 {
p = path.Join(p, r.resource)
}
// Join trims trailing slashes, so preserve r.path's trailing slash for backwards compat if nothing was changed
if len(r.resourceName) != 0 || len(r.subpath) != 0 {
p = path.Join(p, r.resourceName, r.subpath)
}
finalURL := *r.baseURL
finalURL.Path = r.path
finalURL.Path = p
query := url.Values{}
for key, value := range r.params {
query.Add(key, value)
}
if !r.namespaceInPath && len(r.namespace) > 0 {
query.Add("namespace", r.namespace)
}
// sync and timeout are handled specially here, to allow setting them
// in any order.
if r.sync {
@ -452,13 +515,12 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) ([]b
switch resp.StatusCode {
case http.StatusConflict:
if req.Method == "POST" {
// TODO: add Resource() and ResourceName() as Request methods so that we can set these
err = errors.NewAlreadyExists("", "")
err = errors.NewAlreadyExists(r.resource, r.resourceName)
} else {
err = errors.NewConflict("", "", err)
err = errors.NewConflict(r.resource, r.resourceName, err)
}
case http.StatusNotFound:
err = errors.NewNotFound("", "")
err = errors.NewNotFound(r.resource, r.resourceName)
case http.StatusBadRequest:
err = errors.NewBadRequest(err.Error())
}