Client should validate the incoming host value

Convert host:port and URLs passed to client.New() into the proper
values, and return an error if the value is invalid.  Change CLI
to return an error if -master is invalid.  Remove Client.rawRequest
which was not in use, and fix the involved tests. Add NewOrDie

Preserves the behavior of the client to not auth when a non-https
URL is passed (although in the future this should be corrected).
This commit is contained in:
Clayton Coleman
2014-08-28 09:56:38 -04:00
parent fa17697194
commit 818f357128
15 changed files with 203 additions and 172 deletions

View File

@@ -20,7 +20,6 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
@@ -30,7 +29,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/golang/glog"
)
// Interface holds the methods for clients of Kubernetes,
@@ -88,6 +86,28 @@ type Client struct {
*RESTClient
}
// New creates a Kubernetes client. This client works with pods, replication controllers
// and services. It allows operations such as list, get, update and delete on these objects.
// host must be a host string, a host:port combo, or an http or https URL. Passing a prefix
// to a URL will prepend the server path. Returns an error if host cannot be converted to a
// valid URL.
func New(host string, auth *AuthInfo) (*Client, error) {
restClient, err := NewRESTClient(host, auth, "/api/v1beta1/")
if err != nil {
return nil, err
}
return &Client{restClient}, nil
}
// NewOrDie creates a Kubernetes client and panics if the provided host is invalid.
func NewOrDie(host string, auth *AuthInfo) *Client {
client, err := New(host, auth)
if err != nil {
panic(err)
}
return client
}
// StatusErr might get returned from an api call if your request is still being processed
// and hence the expected return data is not available yet.
type StatusErr struct {
@@ -109,20 +129,31 @@ type AuthInfo struct {
// Host is the http://... base for the URL
type RESTClient struct {
host string
prefix string
secure bool
auth *AuthInfo
httpClient *http.Client
Sync bool
PollPeriod time.Duration
Timeout time.Duration
Prefix string
}
// NewRESTClient creates a new RESTClient. This client performs generic REST functions
// such as Get, Put, Post, and Delete on specified paths.
func NewRESTClient(host string, auth *AuthInfo, prefix string) *RESTClient {
func NewRESTClient(host string, auth *AuthInfo, path string) (*RESTClient, error) {
prefix, err := normalizePrefix(host, path)
if err != nil {
return nil, err
}
base := *prefix
base.Path = ""
base.RawQuery = ""
base.Fragment = ""
return &RESTClient{
auth: auth,
host: host,
host: base.String(),
prefix: prefix.Path,
secure: prefix.Scheme == "https",
auth: auth,
httpClient: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
@@ -133,15 +164,36 @@ func NewRESTClient(host string, auth *AuthInfo, prefix string) *RESTClient {
Sync: false,
PollPeriod: time.Second * 2,
Timeout: time.Second * 20,
Prefix: prefix,
}
}, nil
}
// New creates a Kubernetes client. This client works with pods, replication controllers
// and services. It allows operations such as list, get, update and delete on these objects.
func New(host string, auth *AuthInfo) *Client {
return &Client{NewRESTClient(host, auth, "/api/v1beta1/")}
// normalizePrefix ensures the passed initial value is valid
func normalizePrefix(host, prefix string) (*url.URL, error) {
if host == "" {
return nil, fmt.Errorf("host must be a URL or a host:port pair")
}
base := host
hostURL, err := url.Parse(base)
if err != nil {
return nil, err
}
if hostURL.Scheme == "" {
hostURL, err = url.Parse("http://" + base)
if err != nil {
return nil, err
}
if hostURL.Path != "" && hostURL.Path != "/" {
return nil, fmt.Errorf("host must be a URL or a host:port pair: %s", base)
}
}
hostURL.Path += prefix
return hostURL, nil
}
// Secure returns true if the client is configured for secure connections.
func (c *RESTClient) Secure() bool {
return c.secure
}
// Execute a request, adds authentication (if auth != nil), and HTTPS cert ignoring.
@@ -186,55 +238,6 @@ func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
return body, err
}
// Underlying base implementation of performing a request.
// method is the HTTP method (e.g. "GET")
// path is the path on the host to hit
// requestBody is the body of the request. Can be nil.
// target the interface to marshal the JSON response into. Can be nil.
func (c *RESTClient) rawRequest(method, path string, requestBody io.Reader, target interface{}) ([]byte, error) {
reqUrl, err := c.makeURL(path)
if err != nil {
return nil, err
}
request, err := http.NewRequest(method, reqUrl, requestBody)
if err != nil {
return nil, err
}
body, err := c.doRequest(request)
if err != nil {
return body, err
}
if target != nil {
err = api.DecodeInto(body, target)
}
if err != nil {
glog.Infof("Failed to parse: %s\n", string(body))
// FIXME: no need to return err here?
}
return body, err
}
func (c *RESTClient) makeURL(path string) (string, error) {
base := c.host
hostURL, err := url.Parse(base)
if err != nil {
return "", err
}
if hostURL.Scheme == "" {
hostURL, err = url.Parse("http://" + base)
if err != nil {
return "", err
}
if hostURL.Path != "" && hostURL.Path != "/" {
return "", fmt.Errorf("host must be a URL or a host:port pair: %s", base)
}
}
hostURL.Path += c.Prefix + path
return hostURL.String(), nil
}
// ListPods takes a selector, and returns the list of pods that match that selector
func (c *Client) ListPods(selector labels.Selector) (result api.PodList, err error) {
err = c.Get().Path("pods").SelectorParam("labels", selector).Do().Into(&result)