From b1d8a41049e0788d7db9fee6f573ad3395f5447b Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Sat, 21 Jun 2014 14:23:14 -0700 Subject: [PATCH] Add new api usage mechanism. --- cmd/cloudcfg/cloudcfg.go | 42 +++++----- pkg/cloudcfg/cloudcfg.go | 130 +++++++++++++++++++++++++++++++ pkg/cloudcfg/resource_printer.go | 22 ++++++ 3 files changed, 174 insertions(+), 20 deletions(-) diff --git a/cmd/cloudcfg/cloudcfg.go b/cmd/cloudcfg/cloudcfg.go index 8ed97d4179..eaad738186 100644 --- a/cmd/cloudcfg/cloudcfg.go +++ b/cmd/cloudcfg/cloudcfg.go @@ -139,27 +139,34 @@ func executeAPIRequest(method string, auth *kube_client.AuthInfo) bool { return *httpServer + path.Join("/api/v1beta1", storage) } - var request *http.Request - var err error + verb := "" switch method { case "get", "list": - url := readUrl(parseStorage()) - if len(*selector) > 0 && method == "list" { - url = url + "?labels=" + *selector - } - request, err = http.NewRequest("GET", url, nil) + verb = "GET" case "delete": - request, err = http.NewRequest("DELETE", readUrl(parseStorage()), nil) + verb = "DELETE" case "create": - storage := parseStorage() - request, err = cloudcfg.RequestWithBodyData(readConfig(storage), readUrl(storage), "POST") + verb = "POST" case "update": - storage := parseStorage() - request, err = cloudcfg.RequestWithBodyData(readConfig(storage), readUrl(storage), "PUT") + verb = "PUT" default: return false } + s := cloudcfg.New(*httpServer, auth) + r := s.Verb(verb). + Path("api/v1beta1"). + Path(parseStorage()). + Selector(*selector) + if method == "create" || method == "update" { + r.Body(readConfig(parseStorage())) + } + obj, err := r.Do() + if err != nil { + log.Fatalf("Got request error: %v\n", err) + return false + } + var printer cloudcfg.ResourcePrinter if *json { printer = &cloudcfg.IdentityPrinter{} @@ -169,15 +176,10 @@ func executeAPIRequest(method string, auth *kube_client.AuthInfo) bool { printer = &cloudcfg.HumanReadablePrinter{} } - var body []byte - if body, err = cloudcfg.DoRequest(request, auth); err == nil { - if err = printer.Print(body, os.Stdout); err != nil { - log.Fatalf("Failed to print: %#v\nRaw received text:\n%v\n", err, string(body)) - } - fmt.Print("\n") - } else { - log.Fatalf("Error: %#v %s", err, body) + if err = printer.PrintObj(obj, os.Stdout); err != nil { + log.Fatalf("Failed to print: %#v\nRaw received text:\n%v\n", err, string(body)) } + fmt.Print("\n") return true } diff --git a/pkg/cloudcfg/cloudcfg.go b/pkg/cloudcfg/cloudcfg.go index 6925b62d5c..b2d2df6fea 100644 --- a/pkg/cloudcfg/cloudcfg.go +++ b/pkg/cloudcfg/cloudcfg.go @@ -21,16 +21,20 @@ 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" ) @@ -90,6 +94,132 @@ 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, + } +} + +// 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 := path.Join(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? diff --git a/pkg/cloudcfg/resource_printer.go b/pkg/cloudcfg/resource_printer.go index 6f2643cacc..9ca587116b 100644 --- a/pkg/cloudcfg/resource_printer.go +++ b/pkg/cloudcfg/resource_printer.go @@ -32,6 +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) } // Identity printer simply copies the body out to the output stream @@ -42,6 +43,14 @@ func (i *IdentityPrinter) Print(data []byte, w io.Writer) error { return err } +func (i *IdentityPrinter) PrintObj(obj interface{}, output io.Writer) error { + data, err := api.EncodeIndent(obj) + if err != nil { + return err + } + return i.Print(data, output) +} + // YAMLPrinter parses JSON, and re-formats as YAML type YAMLPrinter struct{} @@ -58,6 +67,15 @@ func (y *YAMLPrinter) Print(data []byte, w io.Writer) error { return err } +func (y *YAMLPrinter) PrintObj(obj interface{}, w io.Writer) error { + output, err := yaml.Marshal(obj) + if err != nil { + return err + } + _, err = fmt.Fprint(w, string(output)) + return err +} + // HumanReadablePrinter attempts to provide more elegant output type HumanReadablePrinter struct{} @@ -168,6 +186,10 @@ func (h *HumanReadablePrinter) Print(data []byte, output io.Writer) error { if err != nil { return err } + return h.PrintObj(obj, output) +} + +func (h *HumanReadablePrinter) PrintObj(obj interface{}, output io.Writer) { switch o := obj.(type) { case *api.Pod: h.printHeader(podColumns, w)