diff --git a/cmd/cloudcfg/cloudcfg.go b/cmd/cloudcfg/cloudcfg.go index eaad738186..c27d7b26a9 100644 --- a/cmd/cloudcfg/cloudcfg.go +++ b/cmd/cloudcfg/cloudcfg.go @@ -21,10 +21,8 @@ import ( "fmt" "io/ioutil" "log" - "net/http" "net/url" "os" - "path" "strconv" "strings" "time" @@ -135,10 +133,6 @@ func executeAPIRequest(method string, auth *kube_client.AuthInfo) bool { return strings.Trim(flag.Arg(1), "/") } - readUrl := func(storage string) string { - return *httpServer + path.Join("/api/v1beta1", storage) - } - verb := "" switch method { case "get", "list": @@ -177,7 +171,7 @@ func executeAPIRequest(method string, auth *kube_client.AuthInfo) bool { } if err = printer.PrintObj(obj, os.Stdout); err != nil { - log.Fatalf("Failed to print: %#v\nRaw received text:\n%v\n", err, string(body)) + log.Fatalf("Failed to print: %#v\nRaw received object:\n%#v\n", err, obj) } fmt.Print("\n") diff --git a/pkg/cloudcfg/cloudcfg.go b/pkg/cloudcfg/cloudcfg.go index 07a6bd16ca..0d17707d7a 100644 --- a/pkg/cloudcfg/cloudcfg.go +++ b/pkg/cloudcfg/cloudcfg.go @@ -21,20 +21,16 @@ import ( "crypto/tls" "encoding/json" "fmt" - "io" "io/ioutil" "log" "net/http" - "net/url" "os" - "path" "strconv" "strings" "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" - "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "gopkg.in/v1/yaml" ) @@ -94,137 +90,9 @@ func Update(name string, client client.ClientInterface, updatePeriod time.Durati return nil } -// Server contains info locating a kubernetes api server. -// Example usage: -// auth, err := LoadAuth(filename) -// s := New(url, auth) -// resp, err := s.Verb("GET"). -// Path("api/v1beta1"). -// Path("pods"). -// Selector("area=staging"). -// Timeout(10*time.Second). -// Do() -// list, ok := resp.(api.PodList) -type Server struct { - auth *client.AuthInfo - rawUrl string -} - -// Create a new server object. -func New(serverUrl string, auth *client.AuthInfo) *Server { - return &Server{ - auth: auth, - rawUrl: serverUrl, - } -} - -// Begin a request with a verb (GET, POST, PUT, DELETE) -func (s *Server) Verb(verb string) *Request { - return &Request{ - verb: verb, - s: s, - path: "/", - } -} - -// Request allows for building up a request to a server in a chained fashion. -type Request struct { - s *Server - err error - verb string - path string - body interface{} - selector labels.Selector - timeout time.Duration -} - -// Append an item to the request path. You must call Path at least once. -func (r *Request) Path(item string) *Request { - if r.err != nil { - return r - } - r.path = path.Join(r.path, item) - return r -} - -// Use the given item as a resource label selector. Optional. -func (r *Request) Selector(item string) *Request { - if r.err != nil { - return r - } - r.selector, r.err = labels.ParseSelector(item) - return r -} - -// Use the given duration as a timeout. Optional. -func (r *Request) Timeout(d time.Duration) *Request { - if r.err != nil { - return r - } - r.timeout = d - return r -} - -// Use obj as the body of the request. Optional. -// If obj is a string, try to read a file of that name. -// If obj is a []byte, send it directly. -// Otherwise, assume obj is an api type and marshall it correctly. -func (r *Request) Body(obj interface{}) *Request { - if r.err != nil { - return r - } - r.body = obj - return r -} - -// Format and xecute the request. Returns the API object received, or an error. -func (r *Request) Do() (interface{}, error) { - if r.err != nil { - return nil, r.err - } - finalUrl := r.s.rawUrl + r.path - query := url.Values{} - if r.selector != nil { - query.Add("labels", r.selector.String()) - } - if r.timeout != 0 { - query.Add("timeout", r.timeout.String()) - } - finalUrl += "?" + query.Encode() - var body io.Reader - if r.body != nil { - switch t := r.body.(type) { - case string: - data, err := ioutil.ReadFile(t) - if err != nil { - return nil, err - } - body = bytes.NewBuffer(data) - case []byte: - body = bytes.NewBuffer(t) - default: - data, err := api.Encode(r.body) - if err != nil { - return nil, err - } - body = bytes.NewBuffer(data) - } - } - req, err := http.NewRequest(r.verb, finalUrl, body) - if err != nil { - return nil, err - } - str, err := DoRequest(req, r.s.auth) - if err != nil { - return nil, err - } - return api.Decode([]byte(str)) -} - // RequestWithBody is a helper method that creates an HTTP request with the specified url, method // and a body read from 'configFile' -// FIXME: need to be public API? -func RequestWithBody(configFile, url, method string) (*http.Request, error) { +func requestWithBody(configFile, url, method string) (*http.Request, error) { if len(configFile) == 0 { return nil, fmt.Errorf("empty config file.") } @@ -232,19 +100,19 @@ func RequestWithBody(configFile, url, method string) (*http.Request, error) { if err != nil { return nil, err } - return RequestWithBodyData(data, url, method) + return requestWithBodyData(data, url, method) } // RequestWithBodyData is a helper method that creates an HTTP request with the specified url, method // and body data -func RequestWithBodyData(data []byte, url, method string) (*http.Request, error) { +func requestWithBodyData(data []byte, url, method string) (*http.Request, error) { request, err := http.NewRequest(method, url, bytes.NewBuffer(data)) request.ContentLength = int64(len(data)) return request, err } // Execute a request, adds authentication (if auth != nil), and HTTPS cert ignoring. -func DoRequest(request *http.Request, auth *client.AuthInfo) ([]byte, error) { +func doRequest(request *http.Request, auth *client.AuthInfo) ([]byte, error) { if auth != nil { request.SetBasicAuth(auth.User, auth.Password) } diff --git a/pkg/cloudcfg/cloudcfg_test.go b/pkg/cloudcfg/cloudcfg_test.go index 9ccff33d69..1379530913 100644 --- a/pkg/cloudcfg/cloudcfg_test.go +++ b/pkg/cloudcfg/cloudcfg_test.go @@ -22,9 +22,7 @@ import ( "net/http" "net/http/httptest" "os" - "reflect" "testing" - "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" @@ -159,7 +157,7 @@ func TestDoRequest(t *testing.T) { testServer := httptest.NewTLSServer(&fakeHandler) request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil) auth := client.AuthInfo{User: "user", Password: "pass"} - body, err := DoRequest(request, &auth) + body, err := doRequest(request, &auth) if request.Header["Authorization"] == nil { t.Errorf("Request is missing authorization header: %#v", *request) } @@ -172,43 +170,6 @@ func TestDoRequest(t *testing.T) { fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil) } -func TestDoRequestNewWay(t *testing.T) { - reqBody := "request body" - expectedObj := &api.Service{Port: 12345} - expectedBody, _ := api.Encode(expectedObj) - fakeHandler := util.FakeHandler{ - StatusCode: 200, - ResponseBody: string(expectedBody), - T: t, - } - testServer := httptest.NewTLSServer(&fakeHandler) - auth := client.AuthInfo{User: "user", Password: "pass"} - s := New(testServer.URL, &auth) - obj, err := s.Verb("POST"). - Path("foo/bar"). - Path("baz"). - Selector("name=foo"). - Timeout(time.Second). - Body([]byte(reqBody)). - Do() - if err != nil { - t.Errorf("Unexpected error: %v %#v", err, err) - return - } - if obj == nil { - t.Error("nil obj") - } else if !reflect.DeepEqual(obj, expectedObj) { - t.Errorf("Expected: %#v, got %#v", expectedObj, obj) - } - fakeHandler.ValidateRequest(t, "/foo/bar/baz", "POST", &reqBody) - if fakeHandler.RequestReceived.URL.RawQuery != "labels=name%3Dfoo&timeout=1s" { - t.Errorf("Unexpected query: %v", fakeHandler.RequestReceived.URL.RawQuery) - } - if fakeHandler.RequestReceived.Header["Authorization"] == nil { - t.Errorf("Request is missing authorization header: %#v", *fakeHandler.RequestReceived) - } -} - func TestRunController(t *testing.T) { fakeClient := FakeKubeClient{} name := "name" @@ -323,7 +284,7 @@ func TestCloudCfgDeleteControllerWithReplicas(t *testing.T) { } func TestRequestWithBodyNoSuchFile(t *testing.T) { - request, err := RequestWithBody("non/existent/file.json", "http://www.google.com", "GET") + request, err := requestWithBody("non/existent/file.json", "http://www.google.com", "GET") if request != nil { t.Error("Unexpected non-nil result") } @@ -372,7 +333,7 @@ func TestRequestWithBody(t *testing.T) { expectNoError(t, err) _, err = file.Write(data) expectNoError(t, err) - request, err := RequestWithBody(file.Name(), "http://www.google.com", "GET") + request, err := requestWithBody(file.Name(), "http://www.google.com", "GET") if request == nil { t.Error("Unexpected nil result") } diff --git a/pkg/cloudcfg/request.go b/pkg/cloudcfg/request.go new file mode 100644 index 0000000000..cea3d98a0a --- /dev/null +++ b/pkg/cloudcfg/request.go @@ -0,0 +1,158 @@ +/* +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. +*/ + +package cloudcfg + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "net/url" + "path" + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" +) + +// Server contains info locating a kubernetes api server. +// Example usage: +// auth, err := LoadAuth(filename) +// s := New(url, auth) +// resp, err := s.Verb("GET"). +// Path("api/v1beta1"). +// Path("pods"). +// Selector("area=staging"). +// Timeout(10*time.Second). +// Do() +// list, ok := resp.(api.PodList) +type Server struct { + auth *client.AuthInfo + rawUrl string +} + +// Create a new server object. +func New(serverUrl string, auth *client.AuthInfo) *Server { + return &Server{ + auth: auth, + rawUrl: serverUrl, + } +} + +// Begin a request with a verb (GET, POST, PUT, DELETE) +func (s *Server) Verb(verb string) *Request { + return &Request{ + verb: verb, + s: s, + path: "/", + } +} + +// Request allows for building up a request to a server in a chained fashion. +type Request struct { + s *Server + err error + verb string + path string + body interface{} + selector labels.Selector + timeout time.Duration +} + +// Append an item to the request path. You must call Path at least once. +func (r *Request) Path(item string) *Request { + if r.err != nil { + return r + } + r.path = path.Join(r.path, item) + return r +} + +// Use the given item as a resource label selector. Optional. +func (r *Request) Selector(item string) *Request { + if r.err != nil { + return r + } + r.selector, r.err = labels.ParseSelector(item) + return r +} + +// Use the given duration as a timeout. Optional. +func (r *Request) Timeout(d time.Duration) *Request { + if r.err != nil { + return r + } + r.timeout = d + return r +} + +// Use obj as the body of the request. Optional. +// If obj is a string, try to read a file of that name. +// If obj is a []byte, send it directly. +// Otherwise, assume obj is an api type and marshall it correctly. +func (r *Request) Body(obj interface{}) *Request { + if r.err != nil { + return r + } + r.body = obj + return r +} + +// Format and xecute the request. Returns the API object received, or an error. +func (r *Request) Do() (interface{}, error) { + if r.err != nil { + return nil, r.err + } + finalUrl := r.s.rawUrl + r.path + query := url.Values{} + if r.selector != nil { + query.Add("labels", r.selector.String()) + } + if r.timeout != 0 { + query.Add("timeout", r.timeout.String()) + } + finalUrl += "?" + query.Encode() + var body io.Reader + if r.body != nil { + switch t := r.body.(type) { + case string: + data, err := ioutil.ReadFile(t) + if err != nil { + return nil, err + } + body = bytes.NewBuffer(data) + case []byte: + body = bytes.NewBuffer(t) + default: + data, err := api.Encode(r.body) + if err != nil { + return nil, err + } + body = bytes.NewBuffer(data) + } + } + req, err := http.NewRequest(r.verb, finalUrl, body) + if err != nil { + return nil, err + } + str, err := doRequest(req, r.s.auth) + if err != nil { + return nil, err + } + return api.Decode([]byte(str)) +} diff --git a/pkg/cloudcfg/request_test.go b/pkg/cloudcfg/request_test.go new file mode 100644 index 0000000000..d05fbe6316 --- /dev/null +++ b/pkg/cloudcfg/request_test.go @@ -0,0 +1,104 @@ +/* +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. +*/ + +package cloudcfg + +import ( + "net/http/httptest" + "reflect" + "testing" + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +func TestDoRequestNewWay(t *testing.T) { + reqBody := "request body" + expectedObj := &api.Service{Port: 12345} + expectedBody, _ := api.Encode(expectedObj) + fakeHandler := util.FakeHandler{ + StatusCode: 200, + ResponseBody: string(expectedBody), + T: t, + } + testServer := httptest.NewTLSServer(&fakeHandler) + auth := client.AuthInfo{User: "user", Password: "pass"} + s := New(testServer.URL, &auth) + obj, err := s.Verb("POST"). + Path("foo/bar"). + Path("baz"). + Selector("name=foo"). + Timeout(time.Second). + Body([]byte(reqBody)). + Do() + if err != nil { + t.Errorf("Unexpected error: %v %#v", err, err) + return + } + if obj == nil { + t.Error("nil obj") + } else if !reflect.DeepEqual(obj, expectedObj) { + t.Errorf("Expected: %#v, got %#v", expectedObj, obj) + } + fakeHandler.ValidateRequest(t, "/foo/bar/baz", "POST", &reqBody) + if fakeHandler.RequestReceived.URL.RawQuery != "labels=name%3Dfoo&timeout=1s" { + t.Errorf("Unexpected query: %v", fakeHandler.RequestReceived.URL.RawQuery) + } + if fakeHandler.RequestReceived.Header["Authorization"] == nil { + t.Errorf("Request is missing authorization header: %#v", *fakeHandler.RequestReceived) + } +} + +func TestDoRequestNewWayObj(t *testing.T) { + reqObj := &api.Pod{} + reqBodyExpected, _ := api.Encode(reqObj) + expectedObj := &api.Service{Port: 12345} + expectedBody, _ := api.Encode(expectedObj) + fakeHandler := util.FakeHandler{ + StatusCode: 200, + ResponseBody: string(expectedBody), + T: t, + } + testServer := httptest.NewTLSServer(&fakeHandler) + auth := client.AuthInfo{User: "user", Password: "pass"} + s := New(testServer.URL, &auth) + obj, err := s.Verb("POST"). + Path("foo/bar"). + Path("baz"). + Selector("name=foo"). + Timeout(time.Second). + Body(reqObj). + Do() + if err != nil { + t.Errorf("Unexpected error: %v %#v", err, err) + return + } + if obj == nil { + t.Error("nil obj") + } else if !reflect.DeepEqual(obj, expectedObj) { + t.Errorf("Expected: %#v, got %#v", expectedObj, obj) + } + tmpStr := string(reqBodyExpected) + fakeHandler.ValidateRequest(t, "/foo/bar/baz", "POST", &tmpStr) + if fakeHandler.RequestReceived.URL.RawQuery != "labels=name%3Dfoo&timeout=1s" { + t.Errorf("Unexpected query: %v", fakeHandler.RequestReceived.URL.RawQuery) + } + if fakeHandler.RequestReceived.Header["Authorization"] == nil { + t.Errorf("Request is missing authorization header: %#v", *fakeHandler.RequestReceived) + } +} diff --git a/pkg/cloudcfg/resource_printer.go b/pkg/cloudcfg/resource_printer.go index 58bffc48de..281abf7417 100644 --- a/pkg/cloudcfg/resource_printer.go +++ b/pkg/cloudcfg/resource_printer.go @@ -32,7 +32,7 @@ import ( type ResourcePrinter interface { // Print receives an arbitrary JSON body, formats it and prints it to a writer Print([]byte, io.Writer) error - PrintObj(interface{}, io.Writer) + PrintObj(interface{}, io.Writer) error } // Identity printer simply copies the body out to the output stream