Encode/decode working everywhere now.

This commit is contained in:
Daniel Smith 2014-06-20 16:50:56 -07:00
parent 14361e336a
commit 41534c1cc5
14 changed files with 98 additions and 104 deletions

View File

@ -168,7 +168,7 @@ func executeAPIRequest(method string, auth *kube_client.AuthInfo) bool {
printer = &cloudcfg.HumanReadablePrinter{} printer = &cloudcfg.HumanReadablePrinter{}
} }
var body string var body []byte
if body, err = cloudcfg.DoRequest(request, auth); err == nil { if body, err = cloudcfg.DoRequest(request, auth); err == nil {
if err = printer.Print(body, os.Stdout); 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)) log.Fatalf("Failed to print: %#v\nRaw received text:\n%v\n", err, string(body))

View File

@ -20,6 +20,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"gopkg.in/v1/yaml"
) )
var knownTypes = map[string]reflect.Type{} var knownTypes = map[string]reflect.Type{}
@ -29,6 +31,7 @@ func init() {
PodList{}, Pod{}, PodList{}, Pod{},
ReplicationControllerList{}, ReplicationController{}, ReplicationControllerList{}, ReplicationController{},
ServiceList{}, Service{}, ServiceList{}, Service{},
Status{},
) )
} }
@ -117,7 +120,8 @@ func Decode(data []byte) (interface{}, error) {
findKind := struct { findKind := struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}{} }{}
err := json.Unmarshal(data, &findKind) // yaml is a superset of json, so we use it to decode here. That way, we understand both.
err := yaml.Unmarshal(data, &findKind)
if err != nil { if err != nil {
return nil, fmt.Errorf("Couldn't get kind: %#v", err) return nil, fmt.Errorf("Couldn't get kind: %#v", err)
} }
@ -126,7 +130,7 @@ func Decode(data []byte) (interface{}, error) {
return nil, fmt.Errorf("%v is not a known type", findKind.Kind) return nil, fmt.Errorf("%v is not a known type", findKind.Kind)
} }
obj := reflect.New(objType).Interface() obj := reflect.New(objType).Interface()
err = json.Unmarshal(data, obj) err = yaml.Unmarshal(data, obj)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -143,7 +147,7 @@ func Decode(data []byte) (interface{}, error) {
// if data.Kind is set and doesn't match the type of obj. Obj should be a // if data.Kind is set and doesn't match the type of obj. Obj should be a
// pointer to an api type. // pointer to an api type.
func DecodeInto(data []byte, obj interface{}) error { func DecodeInto(data []byte, obj interface{}) error {
err := json.Unmarshal(data, obj) err := yaml.Unmarshal(data, obj)
if err != nil { if err != nil {
return err return err
} }

View File

@ -61,7 +61,19 @@ func TestTypes(t *testing.T) {
}, },
}, },
&Service{}, &Service{},
&ServiceList{}, &ServiceList{
Items: []Service{
{
Labels: map[string]string{
"foo": "bar",
},
}, {
Labels: map[string]string{
"foo": "baz",
},
},
},
},
&ReplicationControllerList{}, &ReplicationControllerList{},
&ReplicationController{}, &ReplicationController{},
&PodList{}, &PodList{},

View File

@ -174,3 +174,21 @@ type Endpoints struct {
Name string Name string
Endpoints []string Endpoints []string
} }
// Status is a return value for calls that don't return other objects.
// Arguably, this could go in apiserver, but I'm including it here so clients needn't
// import both.
type Status struct {
JSONBase `json:",inline" yaml:",inline"`
// One of: "success", "failure", "working" (for operations not yet completed)
// TODO: if "working", include an operation identifier so final status can be
// checked.
Status string `json:"status,omitempty" yaml:"status,omitempty"`
}
// Values of Status.Status
const (
StatusSuccess = "success"
StatusFailure = "failure"
StatusWorking = "working"
)

View File

@ -50,11 +50,6 @@ func MakeAsync(fn func() interface{}) <-chan interface{} {
return channel return channel
} }
// Status is a return value for calls that don't return other objects
type Status struct {
Success bool
}
// ApiServer is an HTTPHandler that delegates to RESTStorage objects. // ApiServer is an HTTPHandler that delegates to RESTStorage objects.
// It handles URLs of the form: // It handles URLs of the form:
// ${prefix}/${storage_key}[/${object_name}] // ${prefix}/${storage_key}[/${object_name}]
@ -248,7 +243,7 @@ func (server *ApiServer) handleREST(parts []string, requestUrl *url.URL, req *ht
} }
out, err := storage.Delete(parts[1]) out, err := storage.Delete(parts[1])
var obj interface{} var obj interface{}
obj = Status{Success: true} obj = api.Status{Status: api.StatusSuccess}
if err == nil && sync { if err == nil && sync {
obj, err = server.waitForObject(out, timeout) obj, err = server.waitForObject(out, timeout)
} }

View File

@ -42,13 +42,13 @@ func expectNoError(t *testing.T, err error) {
} }
type Simple struct { type Simple struct {
JSONBase api.JSONBase `json:",inline"` api.JSONBase `yaml:",inline" json:",inline"`
Name string Name string `yaml:"name,omitempty" json:"name,omitempty"`
} }
type SimpleList struct { type SimpleList struct {
JSONBase api.JSONBase `json:",inline"` api.JSONBase `yaml:",inline" json:",inline"`
Items []Simple Items []Simple `yaml:"items,omitempty" json:"items,omitempty"`
} }
type SimpleRESTStorage struct { type SimpleRESTStorage struct {
@ -155,6 +155,7 @@ func TestNonEmptyList(t *testing.T) {
var listOut SimpleList var listOut SimpleList
body, err := extractBody(resp, &listOut) body, err := extractBody(resp, &listOut)
expectNoError(t, err)
if len(listOut.Items) != 1 { if len(listOut.Items) != 1 {
t.Errorf("Unexpected response: %#v", listOut) t.Errorf("Unexpected response: %#v", listOut)
return return

View File

@ -113,7 +113,7 @@ func RequestWithBodyData(data []byte, url, method string) (*http.Request, error)
} }
// Execute a request, adds authentication (if auth != nil), and HTTPS cert ignoring. // Execute a request, adds authentication (if auth != nil), and HTTPS cert ignoring.
func DoRequest(request *http.Request, auth *client.AuthInfo) (string, error) { func DoRequest(request *http.Request, auth *client.AuthInfo) ([]byte, error) {
if auth != nil { if auth != nil {
request.SetBasicAuth(auth.User, auth.Password) request.SetBasicAuth(auth.User, auth.Password)
} }
@ -123,11 +123,11 @@ func DoRequest(request *http.Request, auth *client.AuthInfo) (string, error) {
client := &http.Client{Transport: tr} client := &http.Client{Transport: tr}
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
return "", err return []byte{}, err
} }
defer response.Body.Close() defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body) body, err := ioutil.ReadAll(response.Body)
return string(body), err return body, err
} }
// StopController stops a controller named 'name' by setting replicas to zero // StopController stops a controller named 'name' by setting replicas to zero

View File

@ -164,7 +164,7 @@ func TestDoRequest(t *testing.T) {
if err != nil { if err != nil {
t.Error("Unexpected error") t.Error("Unexpected error")
} }
if body != expectedBody { if string(body) != expectedBody {
t.Errorf("Expected body: '%s', saw: '%s'", expectedBody, body) t.Errorf("Expected body: '%s', saw: '%s'", expectedBody, body)
} }
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil) fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)

View File

@ -1,12 +1,10 @@
package cloudcfg package cloudcfg
import ( import (
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"gopkg.in/v1/yaml"
) )
var storageToType = map[string]reflect.Type{ var storageToType = map[string]reflect.Type{
@ -25,9 +23,9 @@ func ToWireFormat(data []byte, storage string) ([]byte, error) {
} }
obj := reflect.New(prototypeType).Interface() obj := reflect.New(prototypeType).Interface()
err := yaml.Unmarshal(data, obj) err := api.DecodeInto(data, obj)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return json.Marshal(obj) return api.Encode(obj)
} }

View File

@ -1,7 +1,6 @@
package cloudcfg package cloudcfg
import ( import (
"encoding/json"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -16,7 +15,7 @@ func TestParseBadStorage(t *testing.T) {
} }
func DoParseTest(t *testing.T, storage string, obj interface{}) { func DoParseTest(t *testing.T, storage string, obj interface{}) {
json_data, _ := json.Marshal(obj) json_data, _ := api.Encode(obj)
yaml_data, _ := yaml.Marshal(obj) yaml_data, _ := yaml.Marshal(obj)
t.Logf("Intermediate yaml:\n%v\n", string(yaml_data)) t.Logf("Intermediate yaml:\n%v\n", string(yaml_data))

View File

@ -31,23 +31,23 @@ import (
// ResourcePrinter is an interface that knows how to print API resources // ResourcePrinter is an interface that knows how to print API resources
type ResourcePrinter interface { type ResourcePrinter interface {
// Print receives an arbitrary JSON body, formats it and prints it to a writer // Print receives an arbitrary JSON body, formats it and prints it to a writer
Print(string, io.Writer) error Print([]byte, io.Writer) error
} }
// Identity printer simply copies the body out to the output stream // Identity printer simply copies the body out to the output stream
type IdentityPrinter struct{} type IdentityPrinter struct{}
func (i *IdentityPrinter) Print(data string, w io.Writer) error { func (i *IdentityPrinter) Print(data []byte, w io.Writer) error {
_, err := fmt.Fprint(w, data) _, err := w.Write(data)
return err return err
} }
// YAMLPrinter parses JSON, and re-formats as YAML // YAMLPrinter parses JSON, and re-formats as YAML
type YAMLPrinter struct{} type YAMLPrinter struct{}
func (y *YAMLPrinter) Print(data string, w io.Writer) error { func (y *YAMLPrinter) Print(data []byte, w io.Writer) error {
var obj interface{} var obj interface{}
if err := json.Unmarshal([]byte(data), &obj); err != nil { if err := json.Unmarshal(data, &obj); err != nil {
return err return err
} }
output, err := yaml.Marshal(obj) output, err := yaml.Marshal(obj)
@ -64,9 +64,10 @@ type HumanReadablePrinter struct{}
var podColumns = []string{"Name", "Image(s)", "Host", "Labels"} var podColumns = []string{"Name", "Image(s)", "Host", "Labels"}
var replicationControllerColumns = []string{"Name", "Image(s)", "Selector", "Replicas"} var replicationControllerColumns = []string{"Name", "Image(s)", "Selector", "Replicas"}
var serviceColumns = []string{"Name", "Labels", "Selector", "Port"} var serviceColumns = []string{"Name", "Labels", "Selector", "Port"}
var statusColumns = []string{"Status"}
func (h *HumanReadablePrinter) unknown(data string, w io.Writer) error { func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
_, err := fmt.Fprintf(w, "Unknown object: %s", data) _, err := fmt.Fprintf(w, "Unknown object: %s", string(data))
return err return err
} }
@ -90,97 +91,62 @@ func (h *HumanReadablePrinter) makeImageList(manifest api.ContainerManifest) str
return strings.Join(images, ",") return strings.Join(images, ",")
} }
func (h *HumanReadablePrinter) printPod(pod api.Pod, w io.Writer) error { func (h *HumanReadablePrinter) printPod(pod *api.Pod, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
pod.ID, h.makeImageList(pod.DesiredState.Manifest), pod.CurrentState.Host+"/"+pod.CurrentState.HostIP, labels.Set(pod.Labels)) pod.ID, h.makeImageList(pod.DesiredState.Manifest), pod.CurrentState.Host+"/"+pod.CurrentState.HostIP, labels.Set(pod.Labels))
return err return err
} }
func (h *HumanReadablePrinter) printPodList(podList api.PodList, w io.Writer) error { func (h *HumanReadablePrinter) printPodList(podList *api.PodList, w io.Writer) error {
for _, pod := range podList.Items { for _, pod := range podList.Items {
if err := h.printPod(pod, w); err != nil { if err := h.printPod(&pod, w); err != nil {
return err return err
} }
} }
return nil return nil
} }
func (h *HumanReadablePrinter) printReplicationController(ctrl api.ReplicationController, w io.Writer) error { func (h *HumanReadablePrinter) printReplicationController(ctrl *api.ReplicationController, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n",
ctrl.ID, h.makeImageList(ctrl.DesiredState.PodTemplate.DesiredState.Manifest), labels.Set(ctrl.DesiredState.ReplicaSelector), ctrl.DesiredState.Replicas) ctrl.ID, h.makeImageList(ctrl.DesiredState.PodTemplate.DesiredState.Manifest), labels.Set(ctrl.DesiredState.ReplicaSelector), ctrl.DesiredState.Replicas)
return err return err
} }
func (h *HumanReadablePrinter) printReplicationControllerList(list api.ReplicationControllerList, w io.Writer) error { func (h *HumanReadablePrinter) printReplicationControllerList(list *api.ReplicationControllerList, w io.Writer) error {
for _, ctrl := range list.Items { for _, ctrl := range list.Items {
if err := h.printReplicationController(ctrl, w); err != nil { if err := h.printReplicationController(&ctrl, w); err != nil {
return err return err
} }
} }
return nil return nil
} }
func (h *HumanReadablePrinter) printService(svc api.Service, w io.Writer) error { func (h *HumanReadablePrinter) printService(svc *api.Service, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", svc.ID, labels.Set(svc.Labels), labels.Set(svc.Selector), svc.Port) _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", svc.ID, labels.Set(svc.Labels), labels.Set(svc.Selector), svc.Port)
return err return err
} }
func (h *HumanReadablePrinter) printServiceList(list api.ServiceList, w io.Writer) error { func (h *HumanReadablePrinter) printServiceList(list *api.ServiceList, w io.Writer) error {
for _, svc := range list.Items { for _, svc := range list.Items {
if err := h.printService(svc, w); err != nil { if err := h.printService(&svc, w); err != nil {
return err return err
} }
} }
return nil return nil
} }
// TODO replace this with something that returns a concrete printer object, rather than func (h *HumanReadablePrinter) printStatus(status *api.Status, w io.Writer) error {
// having the secondary switch below. err := h.printHeader(statusColumns, w)
func (h *HumanReadablePrinter) extractObject(data, kind string) (interface{}, error) { if err != nil {
// TODO: I think this can be replaced with some reflection and a map[string]type return err
switch kind {
case "cluster#pod":
var obj api.Pod
if err := json.Unmarshal([]byte(data), &obj); err != nil {
return nil, err
}
return obj, nil
case "cluster#podList":
var list api.PodList
if err := json.Unmarshal([]byte(data), &list); err != nil {
return nil, err
}
return list, nil
case "cluster#replicationController":
var ctrl api.ReplicationController
if err := json.Unmarshal([]byte(data), &ctrl); err != nil {
return nil, err
}
return ctrl, nil
case "cluster#replicationControllerList":
var list api.ReplicationControllerList
if err := json.Unmarshal([]byte(data), &list); err != nil {
return nil, err
}
return list, nil
case "cluster#service":
var ctrl api.Service
if err := json.Unmarshal([]byte(data), &ctrl); err != nil {
return nil, err
}
return ctrl, nil
case "cluster#serviceList":
var list api.ServiceList
if err := json.Unmarshal([]byte(data), &list); err != nil {
return nil, err
}
return list, nil
default:
return nil, fmt.Errorf("unknown kind: %s", kind)
} }
_, err = fmt.Fprintf(w, "%v\n", status.Status)
return err
} }
func (h *HumanReadablePrinter) Print(data string, output io.Writer) error { // TODO replace this with something that returns a concrete printer object, rather than
// having the secondary switch below.
func (h *HumanReadablePrinter) Print(data []byte, output io.Writer) error {
w := tabwriter.NewWriter(output, 20, 5, 3, ' ', 0) w := tabwriter.NewWriter(output, 20, 5, 3, ' ', 0)
defer w.Flush() defer w.Flush()
var mapObj map[string]interface{} var mapObj map[string]interface{}
@ -198,30 +164,31 @@ func (h *HumanReadablePrinter) Print(data string, output io.Writer) error {
return fmt.Errorf("unexpected object with no 'kind' field: %s", data) return fmt.Errorf("unexpected object with no 'kind' field: %s", data)
} }
kind := (mapObj["kind"]).(string) obj, err := api.Decode(data)
obj, err := h.extractObject(data, kind)
if err != nil { if err != nil {
return err return err
} }
switch obj.(type) { switch o := obj.(type) {
case api.Pod: case *api.Pod:
h.printHeader(podColumns, w) h.printHeader(podColumns, w)
return h.printPod(obj.(api.Pod), w) return h.printPod(o, w)
case api.PodList: case *api.PodList:
h.printHeader(podColumns, w) h.printHeader(podColumns, w)
return h.printPodList(obj.(api.PodList), w) return h.printPodList(o, w)
case api.ReplicationController: case *api.ReplicationController:
h.printHeader(replicationControllerColumns, w) h.printHeader(replicationControllerColumns, w)
return h.printReplicationController(obj.(api.ReplicationController), w) return h.printReplicationController(o, w)
case api.ReplicationControllerList: case *api.ReplicationControllerList:
h.printHeader(replicationControllerColumns, w) h.printHeader(replicationControllerColumns, w)
return h.printReplicationControllerList(obj.(api.ReplicationControllerList), w) return h.printReplicationControllerList(o, w)
case api.Service: case *api.Service:
h.printHeader(serviceColumns, w) h.printHeader(serviceColumns, w)
return h.printService(obj.(api.Service), w) return h.printService(o, w)
case api.ServiceList: case *api.ServiceList:
h.printHeader(serviceColumns, w) h.printHeader(serviceColumns, w)
return h.printServiceList(obj.(api.ServiceList), w) return h.printServiceList(o, w)
case *api.Status:
return h.printStatus(o, w)
default: default:
return h.unknown(data, w) return h.unknown(data, w)
} }

View File

@ -34,7 +34,7 @@ func MakeControllerRegistryStorage(registry ControllerRegistry) apiserver.RESTSt
} }
func (storage *ControllerRegistryStorage) List(selector labels.Selector) (interface{}, error) { func (storage *ControllerRegistryStorage) List(selector labels.Selector) (interface{}, error) {
result := api.ReplicationControllerList{JSONBase: api.JSONBase{Kind: "cluster#replicationControllerList"}} result := api.ReplicationControllerList{}
controllers, err := storage.registry.ListControllers() controllers, err := storage.registry.ListControllers()
if err == nil { if err == nil {
for _, controller := range controllers { for _, controller := range controllers {
@ -55,7 +55,7 @@ func (storage *ControllerRegistryStorage) Get(id string) (interface{}, error) {
} }
func (storage *ControllerRegistryStorage) Delete(id string) (<-chan interface{}, error) { func (storage *ControllerRegistryStorage) Delete(id string) (<-chan interface{}, error) {
return apiserver.MakeAsync(func() interface{} { return apiserver.Status{Success: true} }), storage.registry.DeleteController(id) return apiserver.MakeAsync(func() interface{} { return api.Status{Status: api.StatusSuccess} }), storage.registry.DeleteController(id)
} }
func (storage *ControllerRegistryStorage) Extract(body []byte) (interface{}, error) { func (storage *ControllerRegistryStorage) Extract(body []byte) (interface{}, error) {

View File

@ -130,7 +130,7 @@ func (storage *PodRegistryStorage) Get(id string) (interface{}, error) {
} }
func (storage *PodRegistryStorage) Delete(id string) (<-chan interface{}, error) { func (storage *PodRegistryStorage) Delete(id string) (<-chan interface{}, error) {
return apiserver.MakeAsync(func() interface{} { return apiserver.Status{Success: true} }), storage.registry.DeletePod(id) return apiserver.MakeAsync(func() interface{} { return api.Status{Status: api.StatusSuccess} }), storage.registry.DeletePod(id)
} }
func (storage *PodRegistryStorage) Extract(body []byte) (interface{}, error) { func (storage *PodRegistryStorage) Extract(body []byte) (interface{}, error) {

View File

@ -99,7 +99,7 @@ func (sr *ServiceRegistryStorage) Delete(id string) (<-chan interface{}, error)
} }
} }
} }
return apiserver.MakeAsync(func() interface{} { return apiserver.Status{Success: true} }), sr.registry.DeleteService(id) return apiserver.MakeAsync(func() interface{} { return api.Status{Status: api.StatusSuccess} }), sr.registry.DeleteService(id)
} }
func (sr *ServiceRegistryStorage) Extract(body []byte) (interface{}, error) { func (sr *ServiceRegistryStorage) Extract(body []byte) (interface{}, error) {