Merge pull request #196 from lavalamp/marshal

Make api able to marshal its types correctly
pull/6/head
brendandburns 2014-06-23 13:22:52 -07:00
commit 49c25a4e28
16 changed files with 373 additions and 134 deletions

View File

@ -169,7 +169,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))

154
pkg/api/helper.go Normal file
View File

@ -0,0 +1,154 @@
/*
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 api
import (
"encoding/json"
"fmt"
"reflect"
"gopkg.in/v1/yaml"
)
var knownTypes = map[string]reflect.Type{}
func init() {
AddKnownTypes(
PodList{},
Pod{},
ReplicationControllerList{},
ReplicationController{},
ServiceList{},
Service{},
Status{},
)
}
func AddKnownTypes(types ...interface{}) {
for _, obj := range types {
t := reflect.TypeOf(obj)
knownTypes[t.Name()] = t
}
}
// Encode turns the given api object into an appropriate JSON string.
// Will return an error if the object doesn't have an embedded JSONBase.
// Obj may be a pointer to a struct, or a struct. If a struct, a copy
// will be made so that the object's Kind field can be set. If a pointer,
// we change the Kind field, marshal, and then set the kind field back to
// "". Having to keep track of the kind field makes tests very annoying,
// so the rule is it's set only in wire format (json), not when in native
// format.
func Encode(obj interface{}) (data []byte, err error) {
obj = checkPtr(obj)
jsonBase, err := prepareEncode(obj)
if err != nil {
return nil, err
}
data, err = json.MarshalIndent(obj, "", " ")
jsonBase.Kind = ""
return data, err
}
func checkPtr(obj interface{}) interface{} {
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Ptr {
return obj
}
v2 := reflect.New(v.Type())
v2.Elem().Set(v)
return v2.Interface()
}
func prepareEncode(obj interface{}) (*JSONBase, error) {
name, jsonBase, err := nameAndJSONBase(obj)
if err != nil {
return nil, err
}
if _, contains := knownTypes[name]; !contains {
return nil, fmt.Errorf("struct %v won't be unmarshalable because it's not in knownTypes", name)
}
jsonBase.Kind = name
return jsonBase, nil
}
// Returns the name of the type (sans pointer), and its kind field. Takes pointer-to-struct..
func nameAndJSONBase(obj interface{}) (string, *JSONBase, error) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
return "", nil, fmt.Errorf("expected pointer, but got %v", v.Type().Name())
}
v = v.Elem()
name := v.Type().Name()
if v.Kind() != reflect.Struct {
return "", nil, fmt.Errorf("expected struct, but got %v", name)
}
jsonBase := v.FieldByName("JSONBase")
if !jsonBase.IsValid() {
return "", nil, fmt.Errorf("struct %v lacks embedded JSON type", name)
}
return name, jsonBase.Addr().Interface().(*JSONBase), nil
}
// Decode converts a JSON string back into a pointer to an api object. Deduces the type
// based upon the Kind field (set by encode).
func Decode(data []byte) (interface{}, error) {
findKind := struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}{}
// 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 {
return nil, fmt.Errorf("Couldn't get kind: %#v", err)
}
objType, found := knownTypes[findKind.Kind]
if !found {
return nil, fmt.Errorf("%v is not a known type", findKind.Kind)
}
obj := reflect.New(objType).Interface()
err = yaml.Unmarshal(data, obj)
if err != nil {
return nil, err
}
_, jsonBase, err := nameAndJSONBase(obj)
if err != nil {
return nil, err
}
// Don't leave these set. Track type with go's type.
jsonBase.Kind = ""
return obj, nil
}
// DecodeInto parses a JSON string and stores it in obj. Returns an error
// if data.Kind is set and doesn't match the type of obj. Obj should be a
// pointer to an api type.
func DecodeInto(data []byte, obj interface{}) error {
err := yaml.Unmarshal(data, obj)
if err != nil {
return err
}
name, jsonBase, err := nameAndJSONBase(obj)
if err != nil {
return err
}
if jsonBase.Kind != "" && jsonBase.Kind != name {
return fmt.Errorf("data had kind %v, but passed object was of type %v", jsonBase.Kind, name)
}
// Don't leave these set. Track type with go's type.
jsonBase.Kind = ""
return nil
}

116
pkg/api/helper_test.go Normal file
View File

@ -0,0 +1,116 @@
/*
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 api
import (
"reflect"
"testing"
)
func runTest(t *testing.T, source interface{}) {
name := reflect.TypeOf(source).Name()
data, err := Encode(source)
if err != nil {
t.Errorf("%v: %v", name, err)
return
}
obj2, err := Decode(data)
if err != nil {
t.Errorf("%v: %v", name, err)
return
}
if !reflect.DeepEqual(source, obj2) {
t.Errorf("%v: wanted %#v, got %#v", name, source, obj2)
return
}
obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
err = DecodeInto(data, obj3)
if err != nil {
t.Errorf("%v: %v", name, err)
return
}
if !reflect.DeepEqual(source, obj3) {
t.Errorf("%v: wanted %#v, got %#v", name, source, obj3)
return
}
}
func TestTypes(t *testing.T) {
// TODO: auto-fill all fields.
table := []interface{}{
&Pod{
JSONBase: JSONBase{
ID: "mylittlepod",
},
Labels: map[string]string{
"name": "pinky",
},
},
&Service{},
&ServiceList{
Items: []Service{
{
Labels: map[string]string{
"foo": "bar",
},
}, {
Labels: map[string]string{
"foo": "baz",
},
},
},
},
&ReplicationControllerList{},
&ReplicationController{},
&PodList{},
}
for _, item := range table {
runTest(t, item)
}
}
func TestNonPtr(t *testing.T) {
obj := interface{}(Pod{Labels: map[string]string{"name": "foo"}})
data, err := Encode(obj)
obj2, err2 := Decode(data)
if err != nil || err2 != nil {
t.Errorf("Failure: %v %v", err2, err2)
}
if _, ok := obj2.(*Pod); !ok {
t.Errorf("Got wrong type")
}
if !reflect.DeepEqual(obj2, &Pod{Labels: map[string]string{"name": "foo"}}) {
t.Errorf("Something changed: %#v", obj2)
}
}
func TestPtr(t *testing.T) {
obj := interface{}(&Pod{Labels: map[string]string{"name": "foo"}})
data, err := Encode(obj)
obj2, err2 := Decode(data)
if err != nil || err2 != nil {
t.Errorf("Failure: %v %v", err2, err2)
}
if _, ok := obj2.(*Pod); !ok {
t.Errorf("Got wrong type")
}
if !reflect.DeepEqual(obj2, &Pod{Labels: map[string]string{"name": "foo"}}) {
t.Errorf("Something changed: %#v", obj2)
}
}
// TODO: test rejection of bad JSON.

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

@ -17,7 +17,6 @@ limitations under the License.
package apiserver package apiserver
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
@ -27,6 +26,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
@ -36,7 +36,7 @@ type RESTStorage interface {
List(labels.Selector) (interface{}, error) List(labels.Selector) (interface{}, error)
Get(id string) (interface{}, error) Get(id string) (interface{}, error)
Delete(id string) (<-chan interface{}, error) Delete(id string) (<-chan interface{}, error)
Extract(body string) (interface{}, error) Extract(body []byte) (interface{}, error)
Create(interface{}) (<-chan interface{}, error) Create(interface{}) (<-chan interface{}, error)
Update(interface{}) (<-chan interface{}, error) Update(interface{}) (<-chan interface{}, error)
} }
@ -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}]
@ -130,7 +125,7 @@ func (server *ApiServer) notFound(req *http.Request, w http.ResponseWriter) {
func (server *ApiServer) write(statusCode int, object interface{}, w http.ResponseWriter) { func (server *ApiServer) write(statusCode int, object interface{}, w http.ResponseWriter) {
w.WriteHeader(statusCode) w.WriteHeader(statusCode)
output, err := json.MarshalIndent(object, "", " ") output, err := api.Encode(object)
if err != nil { if err != nil {
server.error(err, w) server.error(err, w)
return return
@ -143,10 +138,10 @@ func (server *ApiServer) error(err error, w http.ResponseWriter) {
fmt.Fprintf(w, "Internal Error: %#v", err) fmt.Fprintf(w, "Internal Error: %#v", err)
} }
func (server *ApiServer) readBody(req *http.Request) (string, error) { func (server *ApiServer) readBody(req *http.Request) ([]byte, error) {
defer req.Body.Close() defer req.Body.Close()
body, err := ioutil.ReadAll(req.Body) body, err := ioutil.ReadAll(req.Body)
return string(body), err return body, err
} }
func (server *ApiServer) waitForObject(out <-chan interface{}, timeout time.Duration) (interface{}, error) { func (server *ApiServer) waitForObject(out <-chan interface{}, timeout time.Duration) (interface{}, error) {
@ -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

@ -27,9 +27,14 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
) )
func init() {
api.AddKnownTypes(Simple{}, SimpleList{})
}
// TODO: This doesn't reduce typing enough to make it worth the less readable errors. Remove. // TODO: This doesn't reduce typing enough to make it worth the less readable errors. Remove.
func expectNoError(t *testing.T, err error) { func expectNoError(t *testing.T, err error) {
if err != nil { if err != nil {
@ -38,11 +43,13 @@ func expectNoError(t *testing.T, err error) {
} }
type Simple struct { type Simple struct {
Name string api.JSONBase `yaml:",inline" json:",inline"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
} }
type SimpleList struct { type SimpleList struct {
Items []Simple api.JSONBase `yaml:",inline" json:",inline"`
Items []Simple `yaml:"items,omitempty" json:"items,omitempty"`
} }
type SimpleRESTStorage struct { type SimpleRESTStorage struct {
@ -55,7 +62,7 @@ type SimpleRESTStorage struct {
} }
func (storage *SimpleRESTStorage) List(labels.Selector) (interface{}, error) { func (storage *SimpleRESTStorage) List(labels.Selector) (interface{}, error) {
result := SimpleList{ result := &SimpleList{
Items: storage.list, Items: storage.list,
} }
return result, storage.err return result, storage.err
@ -70,9 +77,9 @@ func (storage *SimpleRESTStorage) Delete(id string) (<-chan interface{}, error)
return storage.channel, storage.err return storage.channel, storage.err
} }
func (storage *SimpleRESTStorage) Extract(body string) (interface{}, error) { func (storage *SimpleRESTStorage) Extract(body []byte) (interface{}, error) {
var item Simple var item Simple
json.Unmarshal([]byte(body), &item) api.DecodeInto(body, &item)
return item, storage.err return item, storage.err
} }
@ -91,7 +98,7 @@ func extractBody(response *http.Response, object interface{}) (string, error) {
if err != nil { if err != nil {
return string(body), err return string(body), err
} }
err = json.Unmarshal(body, object) err = api.DecodeInto(body, object)
return string(body), err return string(body), err
} }
@ -149,8 +156,10 @@ 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
} }
if listOut.Items[0].Name != simpleStorage.list[0].Name { if listOut.Items[0].Name != simpleStorage.list[0].Name {
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body)) t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))

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

@ -17,12 +17,10 @@ limitations under the License.
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{
@ -41,9 +39,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

@ -17,7 +17,6 @@ limitations under the License.
package cloudcfg package cloudcfg
import ( import (
"encoding/json"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -32,7 +31,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

@ -17,8 +17,6 @@ limitations under the License.
package registry package registry
import ( import (
"encoding/json"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
@ -36,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 {
@ -53,18 +51,16 @@ func (storage *ControllerRegistryStorage) Get(id string) (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
controller.Kind = "cluster#replicationController"
return controller, err return controller, err
} }
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 string) (interface{}, error) { func (storage *ControllerRegistryStorage) Extract(body []byte) (interface{}, error) {
result := api.ReplicationController{} result := api.ReplicationController{}
err := json.Unmarshal([]byte(body), &result) err := api.DecodeInto(body, &result)
result.Kind = "cluster#replicationController"
return result, err return result, err
} }

View File

@ -122,12 +122,10 @@ func TestExtractControllerJson(t *testing.T) {
ID: "foo", ID: "foo",
}, },
} }
body, err := json.Marshal(controller) body, err := api.Encode(&controller)
expectNoError(t, err) expectNoError(t, err)
controllerOut, err := storage.Extract(string(body)) controllerOut, err := storage.Extract(body)
expectNoError(t, err) expectNoError(t, err)
// Extract adds a Kind
controller.Kind = "cluster#replicationController"
if !reflect.DeepEqual(controller, controllerOut) { if !reflect.DeepEqual(controller, controllerOut) {
t.Errorf("Expected %#v, found %#v", controller, controllerOut) t.Errorf("Expected %#v, found %#v", controller, controllerOut)
} }

View File

@ -17,7 +17,6 @@ limitations under the License.
package registry package registry
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
"strings" "strings"
@ -74,7 +73,6 @@ func (storage *PodRegistryStorage) List(selector labels.Selector) (interface{},
} }
} }
result.Kind = "cluster#podList"
return result, err return result, err
} }
@ -129,18 +127,16 @@ func (storage *PodRegistryStorage) Get(id string) (interface{}, error) {
} }
pod.CurrentState.HostIP = getInstanceIP(storage.cloud, pod.CurrentState.Host) pod.CurrentState.HostIP = getInstanceIP(storage.cloud, pod.CurrentState.Host)
pod.Kind = "cluster#pod"
return pod, err return pod, err
} }
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 string) (interface{}, error) { func (storage *PodRegistryStorage) Extract(body []byte) (interface{}, error) {
pod := api.Pod{} pod := api.Pod{}
err := json.Unmarshal([]byte(body), &pod) err := api.DecodeInto(body, &pod)
pod.Kind = "cluster#pod"
return pod, err return pod, err
} }

View File

@ -17,7 +17,6 @@ limitations under the License.
package registry package registry
import ( import (
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"testing" "testing"
@ -103,12 +102,10 @@ func TestExtractJson(t *testing.T) {
ID: "foo", ID: "foo",
}, },
} }
body, err := json.Marshal(pod) body, err := api.Encode(&pod)
expectNoError(t, err) expectNoError(t, err)
podOut, err := storage.Extract(string(body)) podOut, err := storage.Extract(body)
expectNoError(t, err) expectNoError(t, err)
// Extract adds in a kind
pod.Kind = "cluster#pod"
if !reflect.DeepEqual(pod, podOut) { if !reflect.DeepEqual(pod, podOut) {
t.Errorf("Expected %#v, found %#v", pod, podOut) t.Errorf("Expected %#v, found %#v", pod, podOut)
} }

View File

@ -17,7 +17,6 @@ limitations under the License.
package registry package registry
import ( import (
"encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -64,7 +63,6 @@ func (sr *ServiceRegistryStorage) List(selector labels.Selector) (interface{}, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
list.Kind = "cluster#serviceList"
var filtered []api.Service var filtered []api.Service
for _, service := range list.Items { for _, service := range list.Items {
if selector.Matches(labels.Set(service.Labels)) { if selector.Matches(labels.Set(service.Labels)) {
@ -80,7 +78,6 @@ func (sr *ServiceRegistryStorage) Get(id string) (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
service.Kind = "cluster#service"
return service, err return service, err
} }
@ -102,13 +99,12 @@ 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 string) (interface{}, error) { func (sr *ServiceRegistryStorage) Extract(body []byte) (interface{}, error) {
var svc api.Service var svc api.Service
err := json.Unmarshal([]byte(body), &svc) err := api.DecodeInto(body, &svc)
svc.Kind = "cluster#service"
return svc, err return svc, err
} }