mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
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:
parent
998061677b
commit
afedbba3fc
@ -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())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user