mirror of https://github.com/k3s-io/k3s
Refactor Get and Describe to allow extension of types
Get should use ResourceMapper, allow Printer to be abstracted, and extract Describe as *Describer types.pull/6/head
parent
39882a3555
commit
09cfa364c5
|
@ -23,5 +23,5 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd.RunKubectl(os.Stdout)
|
cmd.NewFactory().Run(os.Stdout)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ func (c *FakePods) List(selector labels.Selector) (*api.PodList, error) {
|
||||||
|
|
||||||
func (c *FakePods) Get(name string) (*api.Pod, error) {
|
func (c *FakePods) Get(name string) (*api.Pod, error) {
|
||||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "get-pod", Value: name})
|
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "get-pod", Value: name})
|
||||||
return &api.Pod{}, nil
|
return &api.Pod{ObjectMeta: api.ObjectMeta{Name: name, Namespace: c.Namespace}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *FakePods) Delete(name string) error {
|
func (c *FakePods) Delete(name string) error {
|
||||||
|
|
|
@ -36,7 +36,7 @@ func (c *FakeServices) List(selector labels.Selector) (*api.ServiceList, error)
|
||||||
|
|
||||||
func (c *FakeServices) Get(name string) (*api.Service, error) {
|
func (c *FakeServices) Get(name string) (*api.Service, error) {
|
||||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "get-service", Value: name})
|
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "get-service", Value: name})
|
||||||
return &api.Service{}, nil
|
return &api.Service{ObjectMeta: api.ObjectMeta{Name: name, Namespace: c.Namespace}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *FakeServices) Create(service *api.Service) (*api.Service, error) {
|
func (c *FakeServices) Create(service *api.Service) (*api.Service, error) {
|
||||||
|
|
|
@ -35,13 +35,38 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types
|
||||||
|
// of resources and different API sets.
|
||||||
type Factory struct {
|
type Factory struct {
|
||||||
Mapper meta.RESTMapper
|
Mapper meta.RESTMapper
|
||||||
Typer runtime.ObjectTyper
|
Typer runtime.ObjectTyper
|
||||||
Client func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error)
|
Client func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error)
|
||||||
|
Describer func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error)
|
||||||
|
Printer func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunKubectl(out io.Writer) {
|
// NewFactory creates a factory with the default Kubernetes resources defined
|
||||||
|
func NewFactory() *Factory {
|
||||||
|
return &Factory{
|
||||||
|
Mapper: latest.RESTMapper,
|
||||||
|
Typer: api.Scheme,
|
||||||
|
Client: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) {
|
||||||
|
return getKubeClient(cmd), nil
|
||||||
|
},
|
||||||
|
Describer: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error) {
|
||||||
|
describer, ok := kubectl.DescriberFor(mapping.Kind, getKubeClient(cmd))
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("No description has been implemented for %q", mapping.Kind)
|
||||||
|
}
|
||||||
|
return describer, nil
|
||||||
|
},
|
||||||
|
Printer: func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) {
|
||||||
|
return kubectl.NewHumanReadablePrinter(noHeaders), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Factory) Run(out io.Writer) {
|
||||||
// Parent command to which all subcommands are added.
|
// Parent command to which all subcommands are added.
|
||||||
cmds := &cobra.Command{
|
cmds := &cobra.Command{
|
||||||
Use: "kubectl",
|
Use: "kubectl",
|
||||||
|
@ -52,15 +77,6 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
|
||||||
Run: runHelp,
|
Run: runHelp,
|
||||||
}
|
}
|
||||||
|
|
||||||
factory := &Factory{
|
|
||||||
Mapper: latest.NewDefaultRESTMapper(),
|
|
||||||
Typer: api.Scheme,
|
|
||||||
Client: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) {
|
|
||||||
// Will handle all resources defined by the command
|
|
||||||
return getKubeClient(cmd), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Globally persistent flags across all subcommands.
|
// Globally persistent flags across all subcommands.
|
||||||
// TODO Change flag names to consts to allow safer lookup from subcommands.
|
// TODO Change flag names to consts to allow safer lookup from subcommands.
|
||||||
// TODO Add a verbose flag that turns on glog logging. Probably need a way
|
// TODO Add a verbose flag that turns on glog logging. Probably need a way
|
||||||
|
@ -78,12 +94,12 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
|
||||||
|
|
||||||
cmds.AddCommand(NewCmdVersion(out))
|
cmds.AddCommand(NewCmdVersion(out))
|
||||||
cmds.AddCommand(NewCmdProxy(out))
|
cmds.AddCommand(NewCmdProxy(out))
|
||||||
cmds.AddCommand(NewCmdGet(out))
|
|
||||||
cmds.AddCommand(NewCmdDescribe(out))
|
|
||||||
|
|
||||||
cmds.AddCommand(factory.NewCmdCreate(out))
|
cmds.AddCommand(f.NewCmdGet(out))
|
||||||
cmds.AddCommand(factory.NewCmdUpdate(out))
|
cmds.AddCommand(f.NewCmdDescribe(out))
|
||||||
cmds.AddCommand(factory.NewCmdDelete(out))
|
cmds.AddCommand(f.NewCmdCreate(out))
|
||||||
|
cmds.AddCommand(f.NewCmdUpdate(out))
|
||||||
|
cmds.AddCommand(f.NewCmdDelete(out))
|
||||||
|
|
||||||
cmds.AddCommand(NewCmdNamespace(out))
|
cmds.AddCommand(NewCmdNamespace(out))
|
||||||
cmds.AddCommand(NewCmdLog(out))
|
cmds.AddCommand(NewCmdLog(out))
|
||||||
|
|
|
@ -47,7 +47,7 @@ Examples:
|
||||||
client, err := f.Client(cmd, mapping)
|
client, err := f.Client(cmd, mapping)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
err = kubectl.NewRESTModifier(client, mapping).Create(namespace, data)
|
err = kubectl.NewRESTHelper(client, mapping).Create(namespace, data)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
fmt.Fprintf(out, "%s\n", name)
|
fmt.Fprintf(out, "%s\n", name)
|
||||||
},
|
},
|
||||||
|
|
|
@ -54,7 +54,7 @@ Examples:
|
||||||
client, err := f.Client(cmd, mapping)
|
client, err := f.Client(cmd, mapping)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
err = kubectl.NewRESTModifier(client, mapping).Delete(namespace, name)
|
err = kubectl.NewRESTHelper(client, mapping).Delete(namespace, name)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
fmt.Fprintf(out, "%s\n", name)
|
fmt.Fprintf(out, "%s\n", name)
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,13 +17,13 @@ limitations under the License.
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCmdDescribe(out io.Writer) *cobra.Command {
|
func (f *Factory) NewCmdDescribe(out io.Writer) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "describe <resource> <id>",
|
Use: "describe <resource> <id>",
|
||||||
Short: "Show details of a specific resource",
|
Short: "Show details of a specific resource",
|
||||||
|
@ -32,13 +32,14 @@ func NewCmdDescribe(out io.Writer) *cobra.Command {
|
||||||
This command joins many API calls together to form a detailed description of a
|
This command joins many API calls together to form a detailed description of a
|
||||||
given resource.`,
|
given resource.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if len(args) < 2 {
|
mapping, namespace, name := ResourceFromArgs(cmd, args, f.Mapper)
|
||||||
usageError(cmd, "Need to supply a resource and an ID")
|
|
||||||
}
|
describer, err := f.Describer(cmd, mapping)
|
||||||
resource := args[0]
|
|
||||||
id := args[1]
|
|
||||||
err := kubectl.Describe(out, getKubeClient(cmd), resource, id)
|
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
|
s, err := describer.Describe(namespace, name)
|
||||||
|
checkErr(err)
|
||||||
|
fmt.Fprintf(out, "%s\n", s)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -20,12 +20,13 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCmdGet(out io.Writer) *cobra.Command {
|
func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "get [(-o|--output=)table|json|yaml|template] [-t <file>|--template=<file>] <resource> [<id>]",
|
Use: "get [(-o|--output=)console|json|yaml|...] <resource> [<id>]",
|
||||||
Short: "Display one or many resources",
|
Short: "Display one or many resources",
|
||||||
Long: `Display one or many resources.
|
Long: `Display one or many resources.
|
||||||
|
|
||||||
|
@ -44,20 +45,24 @@ Examples:
|
||||||
$ kubectl get -f json pod 1234-56-7890-234234-456456
|
$ kubectl get -f json pod 1234-56-7890-234234-456456
|
||||||
<list single pod in json output format>`,
|
<list single pod in json output format>`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var resource, id string
|
mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, f.Mapper)
|
||||||
if len(args) == 0 {
|
|
||||||
usageError(cmd, "Need to supply a resource.")
|
selector := getFlagString(cmd, "selector")
|
||||||
}
|
labels, err := labels.ParseSelector(selector)
|
||||||
if len(args) >= 1 {
|
checkErr(err)
|
||||||
resource = args[0]
|
|
||||||
}
|
client, err := f.Client(cmd, mapping)
|
||||||
if len(args) >= 2 {
|
checkErr(err)
|
||||||
id = args[1]
|
|
||||||
}
|
obj, err := kubectl.NewRESTHelper(client, mapping).Get(namespace, name, labels)
|
||||||
|
checkErr(err)
|
||||||
|
|
||||||
outputFormat := getFlagString(cmd, "output")
|
outputFormat := getFlagString(cmd, "output")
|
||||||
templateFile := getFlagString(cmd, "template")
|
templateFile := getFlagString(cmd, "template")
|
||||||
selector := getFlagString(cmd, "selector")
|
defaultPrinter, err := f.Printer(cmd, mapping, getFlagBool(cmd, "no-headers"))
|
||||||
err := kubectl.Get(out, getKubeClient(cmd).RESTClient, getKubeNamespace(cmd), resource, id, selector, outputFormat, getFlagBool(cmd, "no-headers"), templateFile)
|
checkErr(err)
|
||||||
|
|
||||||
|
err = kubectl.Print(out, obj, outputFormat, templateFile, defaultPrinter)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
@ -37,7 +36,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
|
||||||
|
|
||||||
if len(args) == 2 {
|
if len(args) == 2 {
|
||||||
resource := args[0]
|
resource := args[0]
|
||||||
namespace = api.NamespaceDefault
|
namespace = getKubeNamespace(cmd)
|
||||||
name = args[1]
|
name = args[1]
|
||||||
if len(name) == 0 || len(resource) == 0 {
|
if len(name) == 0 || len(resource) == 0 {
|
||||||
usageError(cmd, "Must specify filename or command line params")
|
usageError(cmd, "Must specify filename or command line params")
|
||||||
|
@ -63,6 +62,62 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResourceFromArgs expects two arguments with a given type, and extracts the fields necessary
|
||||||
|
// to uniquely locate a resource. Displays a usageError if that contract is not satisfied, or
|
||||||
|
// a generic error if any other problems occur.
|
||||||
|
func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
usageError(cmd, "Must provide resource and name command line params")
|
||||||
|
}
|
||||||
|
|
||||||
|
resource := args[0]
|
||||||
|
namespace = getKubeNamespace(cmd)
|
||||||
|
name = args[1]
|
||||||
|
if len(name) == 0 || len(resource) == 0 {
|
||||||
|
usageError(cmd, "Must provide resource and name command line params")
|
||||||
|
}
|
||||||
|
|
||||||
|
version, kind, err := mapper.VersionAndKindForResource(resource)
|
||||||
|
checkErr(err)
|
||||||
|
|
||||||
|
mapping, err = mapper.RESTMapping(version, kind)
|
||||||
|
checkErr(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceFromArgs expects two arguments with a given type, and extracts the fields necessary
|
||||||
|
// to uniquely locate a resource. Displays a usageError if that contract is not satisfied, or
|
||||||
|
// a generic error if any other problems occur.
|
||||||
|
func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string) {
|
||||||
|
if len(args) == 0 || len(args) > 2 {
|
||||||
|
usageError(cmd, "Must provide resource or a resource and name as command line params")
|
||||||
|
}
|
||||||
|
|
||||||
|
resource := args[0]
|
||||||
|
if len(resource) == 0 {
|
||||||
|
usageError(cmd, "Must provide resource or a resource and name as command line params")
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace = getKubeNamespace(cmd)
|
||||||
|
if len(args) == 2 {
|
||||||
|
name = args[1]
|
||||||
|
if len(name) == 0 {
|
||||||
|
usageError(cmd, "Must provide resource or a resource and name as command line params")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version, kind, err := mapper.VersionAndKindForResource(resource)
|
||||||
|
checkErr(err)
|
||||||
|
|
||||||
|
mapping, err = mapper.RESTMapping(version, kind)
|
||||||
|
checkErr(err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceFromFile retrieves the name and namespace from a valid file. If the file does not
|
||||||
|
// resolve to a known type an error is returned. The returned mapping can be used to determine
|
||||||
|
// the correct REST endpoint to modify this resource with.
|
||||||
func ResourceFromFile(filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string, data []byte) {
|
func ResourceFromFile(filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string, data []byte) {
|
||||||
configData, err := readConfigData(filename)
|
configData, err := readConfigData(filename)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
|
@ -47,7 +47,7 @@ Examples:
|
||||||
client, err := f.Client(cmd, mapping)
|
client, err := f.Client(cmd, mapping)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
err = kubectl.NewRESTModifier(client, mapping).Update(namespace, name, true, data)
|
err = kubectl.NewRESTHelper(client, mapping).Update(namespace, name, true, data)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
fmt.Fprintf(out, "%s\n", name)
|
fmt.Fprintf(out, "%s\n", name)
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,7 +18,6 @@ package kubectl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
@ -28,35 +27,62 @@ import (
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Describe(w io.Writer, c client.Interface, resource, id string) error {
|
// Describer generates output for the named resource or an error
|
||||||
var str string
|
// if the output could not be generated.
|
||||||
var err error
|
type Describer interface {
|
||||||
path, err := resolveResource(resolveToPath, resource)
|
Describe(namespace, name string) (output string, err error)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch path {
|
|
||||||
case "pods":
|
|
||||||
str, err = describePod(w, c, id)
|
|
||||||
case "replicationControllers":
|
|
||||||
str, err = describeReplicationController(w, c, id)
|
|
||||||
case "services":
|
|
||||||
str, err = describeService(w, c, id)
|
|
||||||
case "minions":
|
|
||||||
str, err = describeMinion(w, c, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fmt.Fprintf(w, str)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func describePod(w io.Writer, c client.Interface, id string) (string, error) {
|
// Describer returns the default describe functions for each of the standard
|
||||||
// TODO this needs proper namespace support
|
// Kubernetes types.
|
||||||
pod, err := c.Pods(api.NamespaceDefault).Get(id)
|
func DescriberFor(kind string, c *client.Client) (Describer, bool) {
|
||||||
|
switch kind {
|
||||||
|
case "Pod":
|
||||||
|
return &PodDescriber{
|
||||||
|
PodClient: func(namespace string) (client.PodInterface, error) {
|
||||||
|
return c.Pods(namespace), nil
|
||||||
|
},
|
||||||
|
ReplicationControllerClient: func(namespace string) (client.ReplicationControllerInterface, error) {
|
||||||
|
return c.ReplicationControllers(namespace), nil
|
||||||
|
},
|
||||||
|
}, true
|
||||||
|
case "ReplicationController":
|
||||||
|
return &ReplicationControllerDescriber{
|
||||||
|
PodClient: func(namespace string) (client.PodInterface, error) {
|
||||||
|
return c.Pods(namespace), nil
|
||||||
|
},
|
||||||
|
ReplicationControllerClient: func(namespace string) (client.ReplicationControllerInterface, error) {
|
||||||
|
return c.ReplicationControllers(namespace), nil
|
||||||
|
},
|
||||||
|
}, true
|
||||||
|
case "Service":
|
||||||
|
return &ServiceDescriber{
|
||||||
|
ServiceClient: func(namespace string) (client.ServiceInterface, error) {
|
||||||
|
return c.Services(namespace), nil
|
||||||
|
},
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PodDescriber generates information about a pod and the replication controllers that
|
||||||
|
// create it.
|
||||||
|
type PodDescriber struct {
|
||||||
|
PodClient func(namespace string) (client.PodInterface, error)
|
||||||
|
ReplicationControllerClient func(namespace string) (client.ReplicationControllerInterface, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PodDescriber) Describe(namespace, name string) (string, error) {
|
||||||
|
rc, err := d.ReplicationControllerClient(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pc, err := d.PodClient(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pod, err := pc.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -67,19 +93,34 @@ func describePod(w io.Writer, c client.Interface, id string) (string, error) {
|
||||||
fmt.Fprintf(out, "Host:\t%s\n", pod.CurrentState.Host+"/"+pod.CurrentState.HostIP)
|
fmt.Fprintf(out, "Host:\t%s\n", pod.CurrentState.Host+"/"+pod.CurrentState.HostIP)
|
||||||
fmt.Fprintf(out, "Labels:\t%s\n", formatLabels(pod.Labels))
|
fmt.Fprintf(out, "Labels:\t%s\n", formatLabels(pod.Labels))
|
||||||
fmt.Fprintf(out, "Status:\t%s\n", string(pod.CurrentState.Status))
|
fmt.Fprintf(out, "Status:\t%s\n", string(pod.CurrentState.Status))
|
||||||
fmt.Fprintf(out, "Replication Controllers:\t%s\n", getReplicationControllersForLabels(c, labels.Set(pod.Labels)))
|
fmt.Fprintf(out, "Replication Controllers:\t%s\n", getReplicationControllersForLabels(rc, labels.Set(pod.Labels)))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func describeReplicationController(w io.Writer, c client.Interface, id string) (string, error) {
|
// ReplicationControllerDescriber generates information about a replication controller
|
||||||
// TODO this needs proper namespace support
|
// and the pods it has created.
|
||||||
controller, err := c.ReplicationControllers(api.NamespaceDefault).Get(id)
|
type ReplicationControllerDescriber struct {
|
||||||
|
ReplicationControllerClient func(namespace string) (client.ReplicationControllerInterface, error)
|
||||||
|
PodClient func(namespace string) (client.PodInterface, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ReplicationControllerDescriber) Describe(namespace, name string) (string, error) {
|
||||||
|
rc, err := d.ReplicationControllerClient(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pc, err := d.PodClient(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
running, waiting, terminated, err := getPodStatusForReplicationController(c, controller)
|
controller, err := rc.Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
running, waiting, terminated, err := getPodStatusForReplicationController(pc, controller)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -95,8 +136,18 @@ func describeReplicationController(w io.Writer, c client.Interface, id string) (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func describeService(w io.Writer, c client.Interface, id string) (string, error) {
|
// ServiceDescriber generates information about a service.
|
||||||
service, err := c.Services(api.NamespaceDefault).Get(id)
|
type ServiceDescriber struct {
|
||||||
|
ServiceClient func(namespace string) (client.ServiceInterface, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ServiceDescriber) Describe(namespace, name string) (string, error) {
|
||||||
|
c, err := d.ServiceClient(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err := c.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -110,8 +161,17 @@ func describeService(w io.Writer, c client.Interface, id string) (string, error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func describeMinion(w io.Writer, c client.Interface, id string) (string, error) {
|
// MinionDescriber generates information about a minion.
|
||||||
minion, err := getMinion(c, id)
|
type MinionDescriber struct {
|
||||||
|
MinionClient func() (client.MinionInterface, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MinionDescriber) Describe(namespace, name string) (string, error) {
|
||||||
|
mc, err := d.MinionClient()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
minion, err := mc.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -122,29 +182,14 @@ func describeMinion(w io.Writer, c client.Interface, id string) (string, error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// client.Interface doesn't have GetMinion(id) yet so we hack it up.
|
|
||||||
func getMinion(c client.Interface, id string) (*api.Minion, error) {
|
|
||||||
minionList, err := c.Minions().List()
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("Error getting minion info: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, minion := range minionList.Items {
|
|
||||||
if id == minion.Name {
|
|
||||||
return &minion, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Minion %s not found", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all replication controllers whose selectors would match a given set of
|
// Get all replication controllers whose selectors would match a given set of
|
||||||
// labels.
|
// labels.
|
||||||
// TODO Move this to pkg/client and ideally implement it server-side (instead
|
// TODO Move this to pkg/client and ideally implement it server-side (instead
|
||||||
// of getting all RC's and searching through them manually).
|
// of getting all RC's and searching through them manually).
|
||||||
func getReplicationControllersForLabels(c client.Interface, labelsToMatch labels.Labels) string {
|
func getReplicationControllersForLabels(c client.ReplicationControllerInterface, labelsToMatch labels.Labels) string {
|
||||||
// Get all replication controllers.
|
// Get all replication controllers.
|
||||||
// TODO this needs a namespace scope as argument
|
// TODO this needs a namespace scope as argument
|
||||||
rcs, err := c.ReplicationControllers(api.NamespaceDefault).List(labels.Everything())
|
rcs, err := c.List(labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Error getting replication controllers: %v\n", err)
|
glog.Fatalf("Error getting replication controllers: %v\n", err)
|
||||||
}
|
}
|
||||||
|
@ -171,8 +216,8 @@ func getReplicationControllersForLabels(c client.Interface, labelsToMatch labels
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPodStatusForReplicationController(kubeClient client.Interface, controller *api.ReplicationController) (running, waiting, terminated int, err error) {
|
func getPodStatusForReplicationController(c client.PodInterface, controller *api.ReplicationController) (running, waiting, terminated int, err error) {
|
||||||
rcPods, err := kubeClient.Pods(controller.Namespace).List(labels.SelectorFromSet(controller.DesiredState.ReplicaSelector))
|
rcPods, err := c.List(labels.SelectorFromSet(controller.DesiredState.ReplicaSelector))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
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 kubectl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type describeClient struct {
|
||||||
|
T *testing.T
|
||||||
|
Namespace string
|
||||||
|
Err error
|
||||||
|
Fake *client.Fake
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *describeClient) Pod(namespace string) (client.PodInterface, error) {
|
||||||
|
if namespace != c.Namespace {
|
||||||
|
c.T.Errorf("unexpected namespace arg: %s", namespace)
|
||||||
|
}
|
||||||
|
return c.Fake.Pods(namespace), c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *describeClient) ReplicationController(namespace string) (client.ReplicationControllerInterface, error) {
|
||||||
|
if namespace != c.Namespace {
|
||||||
|
c.T.Errorf("unexpected namespace arg: %s", namespace)
|
||||||
|
}
|
||||||
|
return c.Fake.ReplicationControllers(namespace), c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *describeClient) Service(namespace string) (client.ServiceInterface, error) {
|
||||||
|
if namespace != c.Namespace {
|
||||||
|
c.T.Errorf("unexpected namespace arg: %s", namespace)
|
||||||
|
}
|
||||||
|
return c.Fake.Services(namespace), c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDescribePod(t *testing.T) {
|
||||||
|
fake := &client.Fake{}
|
||||||
|
c := &describeClient{T: t, Namespace: "foo", Fake: fake}
|
||||||
|
d := PodDescriber{
|
||||||
|
PodClient: c.Pod,
|
||||||
|
ReplicationControllerClient: c.ReplicationController,
|
||||||
|
}
|
||||||
|
out, err := d.Describe("foo", "bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, "bar") || !strings.Contains(out, "Status:") {
|
||||||
|
t.Errorf("unexpected out: %s", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDescribeService(t *testing.T) {
|
||||||
|
fake := &client.Fake{}
|
||||||
|
c := &describeClient{T: t, Namespace: "foo", Fake: fake}
|
||||||
|
d := ServiceDescriber{
|
||||||
|
ServiceClient: c.Service,
|
||||||
|
}
|
||||||
|
out, err := d.Describe("foo", "bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, "Labels:") || !strings.Contains(out, "bar") {
|
||||||
|
t.Errorf("unexpected out: %s", out)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
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 kubectl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Get(w io.Writer, c *client.RESTClient, namespace string, resource string, id string, selector string, format string, noHeaders bool, templateFile string) error {
|
|
||||||
path, err := resolveResource(resolveToPath, resource)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := c.Verb("GET").Namespace(namespace).Path(path)
|
|
||||||
if len(id) > 0 {
|
|
||||||
r.Path(id)
|
|
||||||
}
|
|
||||||
if len(selector) > 0 {
|
|
||||||
r.ParseSelectorParam("labels", selector)
|
|
||||||
}
|
|
||||||
result := r.Do()
|
|
||||||
obj, err := result.Get()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
printer, err := getPrinter(format, templateFile, noHeaders)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = printer.PrintObj(obj, w); err != nil {
|
|
||||||
body, _ := result.Raw()
|
|
||||||
return fmt.Errorf("Failed to print: %v\nRaw received object:\n%#v\n\nBody received: %v", err, obj, string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -31,8 +31,6 @@ import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||||
|
|
||||||
"gopkg.in/v1/yaml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var apiVersionToUse = "v1beta1"
|
var apiVersionToUse = "v1beta1"
|
||||||
|
@ -132,16 +130,6 @@ func promptForString(field string, r io.Reader) string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateResource(resource, id string) ([]byte, error) {
|
|
||||||
kind, err := resolveResource(resolveToKind, resource)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := fmt.Sprintf(`{"kind": "%s", "apiVersion": "%s", "id": "%s"}`, kind, apiVersionToUse, id)
|
|
||||||
return []byte(s), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Move to labels package.
|
// TODO Move to labels package.
|
||||||
func formatLabels(labelMap map[string]string) string {
|
func formatLabels(labelMap map[string]string) string {
|
||||||
l := labels.Set(labelMap).String()
|
l := labels.Set(labelMap).String()
|
||||||
|
@ -158,90 +146,3 @@ func makeImageList(manifest api.ContainerManifest) string {
|
||||||
}
|
}
|
||||||
return strings.Join(images, ",")
|
return strings.Join(images, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
resolveToPath = "path"
|
|
||||||
resolveToKind = "kind"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Takes a human-friendly reference to a resource and converts it to either a
|
|
||||||
// resource path for an API call or to a Kind to construct a JSON definition.
|
|
||||||
// See usages of the function for more context.
|
|
||||||
//
|
|
||||||
// target is one of the above constants ("path" or "kind") to determine what to
|
|
||||||
// resolve the resource to.
|
|
||||||
//
|
|
||||||
// resource is the human-friendly reference to the resource you want to
|
|
||||||
// convert.
|
|
||||||
func resolveResource(target, resource string) (string, error) {
|
|
||||||
if target != resolveToPath && target != resolveToKind {
|
|
||||||
return "", fmt.Errorf("Unrecognized target to convert to: %s", target)
|
|
||||||
}
|
|
||||||
|
|
||||||
var resolved string
|
|
||||||
var err error
|
|
||||||
// Caseless comparison.
|
|
||||||
resource = strings.ToLower(resource)
|
|
||||||
switch resource {
|
|
||||||
case "pods", "pod", "po":
|
|
||||||
if target == resolveToPath {
|
|
||||||
resolved = "pods"
|
|
||||||
} else {
|
|
||||||
resolved = "Pod"
|
|
||||||
}
|
|
||||||
case "replicationcontrollers", "replicationcontroller", "rc":
|
|
||||||
if target == resolveToPath {
|
|
||||||
resolved = "replicationControllers"
|
|
||||||
} else {
|
|
||||||
resolved = "ReplicationController"
|
|
||||||
}
|
|
||||||
case "services", "service", "se":
|
|
||||||
if target == resolveToPath {
|
|
||||||
resolved = "services"
|
|
||||||
} else {
|
|
||||||
resolved = "Service"
|
|
||||||
}
|
|
||||||
case "minions", "minion", "mi":
|
|
||||||
if target == resolveToPath {
|
|
||||||
resolved = "minions"
|
|
||||||
} else {
|
|
||||||
resolved = "Minion"
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// It might be a GUID, but we don't know how to handle those for now.
|
|
||||||
err = fmt.Errorf("Resource %s not recognized; need pods, replicationControllers, services or minions.", resource)
|
|
||||||
}
|
|
||||||
return resolved, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveKindToResource(kind string) (resource string, err error) {
|
|
||||||
// Determine the REST resource according to the type in data.
|
|
||||||
switch kind {
|
|
||||||
case "Pod":
|
|
||||||
resource = "pods"
|
|
||||||
case "ReplicationController":
|
|
||||||
resource = "replicationControllers"
|
|
||||||
case "Service":
|
|
||||||
resource = "services"
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("Object %s not recognized", kind)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// versionAndKind will return the APIVersion and Kind of the given wire-format
|
|
||||||
// enconding of an APIObject, or an error. This is hacked in until the
|
|
||||||
// migration to v1beta3.
|
|
||||||
func versionAndKind(data []byte) (version, kind string, err error) {
|
|
||||||
findKind := struct {
|
|
||||||
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
|
||||||
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,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 "", "", fmt.Errorf("couldn't get version/kind: %v", err)
|
|
||||||
}
|
|
||||||
return findKind.APIVersion, findKind.Kind, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
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 kubectl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FakeRESTClient struct{}
|
|
||||||
|
|
||||||
func (c *FakeRESTClient) Get() *client.Request {
|
|
||||||
return &client.Request{}
|
|
||||||
}
|
|
||||||
func (c *FakeRESTClient) Put() *client.Request {
|
|
||||||
return &client.Request{}
|
|
||||||
}
|
|
||||||
func (c *FakeRESTClient) Post() *client.Request {
|
|
||||||
return &client.Request{}
|
|
||||||
}
|
|
||||||
func (c *FakeRESTClient) Delete() *client.Request {
|
|
||||||
return &client.Request{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRESTModifierDelete(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
Err bool
|
|
||||||
}{
|
|
||||||
/*{
|
|
||||||
Err: true,
|
|
||||||
},*/
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
client := &FakeRESTClient{}
|
|
||||||
modifier := &RESTModifier{
|
|
||||||
RESTClient: client,
|
|
||||||
}
|
|
||||||
err := modifier.Delete("bar", "foo")
|
|
||||||
switch {
|
|
||||||
case err == nil && test.Err:
|
|
||||||
t.Errorf("Unexpected non-error")
|
|
||||||
continue
|
|
||||||
case err != nil && !test.Err:
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -34,7 +34,20 @@ import (
|
||||||
"gopkg.in/v1/yaml"
|
"gopkg.in/v1/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getPrinter(format, templateFile string, noHeaders bool) (ResourcePrinter, error) {
|
// Print outputs a runtime.Object to an io.Writer in the given format
|
||||||
|
func Print(w io.Writer, obj runtime.Object, format string, templateFile string, defaultPrinter ResourcePrinter) error {
|
||||||
|
printer, err := getPrinter(format, templateFile, defaultPrinter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := printer.PrintObj(obj, w); err != nil {
|
||||||
|
return fmt.Errorf("Failed to print: %v\nRaw received object:\n%#v", err, obj)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrinter(format, templateFile string, defaultPrinter ResourcePrinter) (ResourcePrinter, error) {
|
||||||
var printer ResourcePrinter
|
var printer ResourcePrinter
|
||||||
switch format {
|
switch format {
|
||||||
case "json":
|
case "json":
|
||||||
|
@ -60,7 +73,7 @@ func getPrinter(format, templateFile string, noHeaders bool) (ResourcePrinter, e
|
||||||
Template: tmpl,
|
Template: tmpl,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
printer = NewHumanReadablePrinter(noHeaders)
|
printer = defaultPrinter
|
||||||
}
|
}
|
||||||
return printer, nil
|
return printer, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,13 @@ package kubectl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RESTModifier provides methods for mutating a known or unknown
|
// RESTHelper provides methods for retrieving or mutating a RESTful
|
||||||
// RESTful resource.
|
// resource.
|
||||||
type RESTModifier struct {
|
type RESTHelper struct {
|
||||||
Resource string
|
Resource string
|
||||||
// A RESTClient capable of mutating this resource
|
// A RESTClient capable of mutating this resource
|
||||||
RESTClient RESTClient
|
RESTClient RESTClient
|
||||||
|
@ -34,9 +35,9 @@ type RESTModifier struct {
|
||||||
Versioner runtime.ResourceVersioner
|
Versioner runtime.ResourceVersioner
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRESTModifier creates a RESTModifier from a RESTMapping
|
// NewRESTHelper creates a RESTHelper from a ResourceMapping
|
||||||
func NewRESTModifier(client RESTClient, mapping *meta.RESTMapping) *RESTModifier {
|
func NewRESTHelper(client RESTClient, mapping *meta.RESTMapping) *RESTHelper {
|
||||||
return &RESTModifier{
|
return &RESTHelper{
|
||||||
RESTClient: client,
|
RESTClient: client,
|
||||||
Resource: mapping.Resource,
|
Resource: mapping.Resource,
|
||||||
Codec: mapping.Codec,
|
Codec: mapping.Codec,
|
||||||
|
@ -44,35 +45,39 @@ func NewRESTModifier(client RESTClient, mapping *meta.RESTMapping) *RESTModifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RESTModifier) Delete(namespace, name string) error {
|
func (m *RESTHelper) Get(namespace, name string, selector labels.Selector) (runtime.Object, error) {
|
||||||
return m.RESTClient.Delete().Path(m.Resource).Path(name).Do().Error()
|
return m.RESTClient.Get().Path(m.Resource).Namespace(namespace).Path(name).SelectorParam("labels", selector).Do().Get()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RESTModifier) Create(namespace string, data []byte) error {
|
func (m *RESTHelper) Delete(namespace, name string) error {
|
||||||
return m.RESTClient.Post().Path(m.Resource).Body(data).Do().Error()
|
return m.RESTClient.Delete().Path(m.Resource).Namespace(namespace).Path(name).Do().Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RESTModifier) Update(namespace, name string, overwrite bool, data []byte) error {
|
func (m *RESTHelper) Create(namespace string, data []byte) error {
|
||||||
|
return m.RESTClient.Post().Path(m.Resource).Namespace(namespace).Body(data).Do().Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte) error {
|
||||||
c := m.RESTClient
|
c := m.RESTClient
|
||||||
|
|
||||||
obj, err := m.Codec.Decode(data)
|
obj, err := m.Codec.Decode(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We don't know how to handle this object, but update it anyway
|
// We don't know how to handle this object, but update it anyway
|
||||||
return c.Put().Path(m.Resource).Path(name).Body(data).Do().Error()
|
return updateResource(c, m.Resource, namespace, name, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to version the object based on client logic.
|
// Attempt to version the object based on client logic.
|
||||||
version, err := m.Versioner.ResourceVersion(obj)
|
version, err := m.Versioner.ResourceVersion(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We don't know how to version this object, so send it to the server as is
|
// We don't know how to version this object, so send it to the server as is
|
||||||
return c.Put().Path(m.Resource).Path(name).Body(data).Do().Error()
|
return updateResource(c, m.Resource, namespace, name, data)
|
||||||
}
|
}
|
||||||
if version == "" && overwrite {
|
if version == "" && overwrite {
|
||||||
// Retrieve the current version of the object to overwrite the server object
|
// Retrieve the current version of the object to overwrite the server object
|
||||||
serverObj, err := c.Get().Path(m.Resource).Path(name).Do().Get()
|
serverObj, err := c.Get().Path(m.Resource).Path(name).Do().Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The object does not exist, but we want it to be created
|
// The object does not exist, but we want it to be created
|
||||||
return c.Put().Path(m.Resource).Path(name).Body(data).Do().Error()
|
return updateResource(c, m.Resource, namespace, name, data)
|
||||||
}
|
}
|
||||||
serverVersion, err := m.Versioner.ResourceVersion(serverObj)
|
serverVersion, err := m.Versioner.ResourceVersion(serverObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -88,5 +93,9 @@ func (m *RESTModifier) Update(namespace, name string, overwrite bool, data []byt
|
||||||
data = newData
|
data = newData
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Put().Path(m.Resource).Path(name).Body(data).Do().Error()
|
return updateResource(c, m.Resource, namespace, name, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateResource(c RESTClient, resourcePath, namespace, name string, data []byte) error {
|
||||||
|
return c.Put().Path(resourcePath).Namespace(namespace).Path(name).Body(data).Do().Error()
|
||||||
}
|
}
|
|
@ -0,0 +1,405 @@
|
||||||
|
/*
|
||||||
|
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 kubectl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type httpClientFunc func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (f httpClientFunc) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
return f(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FakeRESTClient struct {
|
||||||
|
Client client.HTTPClient
|
||||||
|
Req *http.Request
|
||||||
|
Resp *http.Response
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeRESTClient) Get() *client.Request {
|
||||||
|
return client.NewRequest(c, "GET", &url.URL{Host: "localhost"}, testapi.Codec())
|
||||||
|
}
|
||||||
|
func (c *FakeRESTClient) Put() *client.Request {
|
||||||
|
return client.NewRequest(c, "PUT", &url.URL{Host: "localhost"}, testapi.Codec())
|
||||||
|
}
|
||||||
|
func (c *FakeRESTClient) Post() *client.Request {
|
||||||
|
return client.NewRequest(c, "POST", &url.URL{Host: "localhost"}, testapi.Codec())
|
||||||
|
}
|
||||||
|
func (c *FakeRESTClient) Delete() *client.Request {
|
||||||
|
return client.NewRequest(c, "DELETE", &url.URL{Host: "localhost"}, testapi.Codec())
|
||||||
|
}
|
||||||
|
func (c *FakeRESTClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
c.Req = req
|
||||||
|
if c.Client != client.HTTPClient(nil) {
|
||||||
|
return c.Client.Do(req)
|
||||||
|
}
|
||||||
|
return c.Resp, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func objBody(obj runtime.Object) io.ReadCloser {
|
||||||
|
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Codec(), obj))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTHelperDelete(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Err bool
|
||||||
|
Req func(*http.Request) bool
|
||||||
|
Resp *http.Response
|
||||||
|
HttpErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
HttpErr: errors.New("failure"),
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Resp: &http.Response{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
Body: objBody(&api.Status{Status: api.StatusFailure}),
|
||||||
|
},
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Resp: &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: objBody(&api.Status{Status: api.StatusSuccess}),
|
||||||
|
},
|
||||||
|
Req: func(req *http.Request) bool {
|
||||||
|
if req.Method != "DELETE" {
|
||||||
|
t.Errorf("unexpected method: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
||||||
|
t.Errorf("url doesn't contain name: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if req.URL.Query().Get("namespace") != "bar" {
|
||||||
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
client := &FakeRESTClient{
|
||||||
|
Resp: test.Resp,
|
||||||
|
Err: test.HttpErr,
|
||||||
|
}
|
||||||
|
modifier := &RESTHelper{
|
||||||
|
RESTClient: client,
|
||||||
|
}
|
||||||
|
err := modifier.Delete("bar", "foo")
|
||||||
|
if (err != nil) != test.Err {
|
||||||
|
t.Errorf("unexpected error: %f %v", test.Err, err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.Req != nil && !test.Req(client.Req) {
|
||||||
|
t.Errorf("unexpected request: %#v", client.Req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTHelperCreate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Resp *http.Response
|
||||||
|
HttpErr error
|
||||||
|
Object runtime.Object
|
||||||
|
|
||||||
|
Err bool
|
||||||
|
Data []byte
|
||||||
|
Req func(*http.Request) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
HttpErr: errors.New("failure"),
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Resp: &http.Response{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
Body: objBody(&api.Status{Status: api.StatusFailure}),
|
||||||
|
},
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Resp: &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: objBody(&api.Status{Status: api.StatusSuccess}),
|
||||||
|
},
|
||||||
|
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||||
|
Req: func(req *http.Request) bool {
|
||||||
|
if req.Method != "POST" {
|
||||||
|
t.Errorf("unexpected method: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if req.URL.Query().Get("namespace") != "bar" {
|
||||||
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
client := &FakeRESTClient{
|
||||||
|
Resp: test.Resp,
|
||||||
|
Err: test.HttpErr,
|
||||||
|
}
|
||||||
|
modifier := &RESTHelper{
|
||||||
|
RESTClient: client,
|
||||||
|
}
|
||||||
|
data := test.Data
|
||||||
|
if test.Object != nil {
|
||||||
|
data = []byte(runtime.EncodeOrDie(testapi.Codec(), test.Object))
|
||||||
|
}
|
||||||
|
err := modifier.Create("bar", data)
|
||||||
|
if (err != nil) != test.Err {
|
||||||
|
t.Errorf("unexpected error: %f %v", test.Err, err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.Req != nil && !test.Req(client.Req) {
|
||||||
|
t.Errorf("unexpected request: %#v", client.Req)
|
||||||
|
}
|
||||||
|
if test.Data != nil {
|
||||||
|
body, _ := ioutil.ReadAll(client.Req.Body)
|
||||||
|
if !reflect.DeepEqual(test.Data, body) {
|
||||||
|
t.Errorf("unexpected body: %s", string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTHelperGet(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Err bool
|
||||||
|
Req func(*http.Request) bool
|
||||||
|
Resp *http.Response
|
||||||
|
HttpErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
HttpErr: errors.New("failure"),
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Resp: &http.Response{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
Body: objBody(&api.Status{Status: api.StatusFailure}),
|
||||||
|
},
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Resp: &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: objBody(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}),
|
||||||
|
},
|
||||||
|
Req: func(req *http.Request) bool {
|
||||||
|
if req.Method != "GET" {
|
||||||
|
t.Errorf("unexpected method: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
||||||
|
t.Errorf("url doesn't contain name: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if req.URL.Query().Get("namespace") != "bar" {
|
||||||
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
client := &FakeRESTClient{
|
||||||
|
Resp: test.Resp,
|
||||||
|
Err: test.HttpErr,
|
||||||
|
}
|
||||||
|
modifier := &RESTHelper{
|
||||||
|
RESTClient: client,
|
||||||
|
}
|
||||||
|
obj, err := modifier.Get("bar", "foo", labels.Everything())
|
||||||
|
if (err != nil) != test.Err {
|
||||||
|
t.Errorf("unexpected error: %f %v", test.Err, err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if obj.(*api.Pod).Name != "foo" {
|
||||||
|
t.Errorf("unexpected object: %#v", obj)
|
||||||
|
}
|
||||||
|
if test.Req != nil && !test.Req(client.Req) {
|
||||||
|
t.Errorf("unexpected request: %#v", client.Req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTHelperUpdate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Resp *http.Response
|
||||||
|
RespFunc httpClientFunc
|
||||||
|
HttpErr error
|
||||||
|
Overwrite bool
|
||||||
|
Object runtime.Object
|
||||||
|
|
||||||
|
ExpectObject runtime.Object
|
||||||
|
Err bool
|
||||||
|
Req func(*http.Request) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
HttpErr: errors.New("failure"),
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||||
|
Resp: &http.Response{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
Body: objBody(&api.Status{Status: api.StatusFailure}),
|
||||||
|
},
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||||
|
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||||
|
Resp: &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: objBody(&api.Status{Status: api.StatusSuccess}),
|
||||||
|
},
|
||||||
|
Req: func(req *http.Request) bool {
|
||||||
|
if req.Method != "PUT" {
|
||||||
|
t.Errorf("unexpected method: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
||||||
|
t.Errorf("url doesn't contain name: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if req.URL.Query().Get("namespace") != "bar" {
|
||||||
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||||
|
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||||
|
|
||||||
|
Overwrite: true,
|
||||||
|
RespFunc: func(req *http.Request) (*http.Response, error) {
|
||||||
|
if req.Method == "PUT" {
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})}, nil
|
||||||
|
}
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
|
||||||
|
},
|
||||||
|
Req: func(req *http.Request) bool {
|
||||||
|
if req.Method != "PUT" {
|
||||||
|
t.Errorf("unexpected method: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
||||||
|
t.Errorf("url doesn't contain name: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if req.URL.Query().Get("namespace") != "bar" {
|
||||||
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||||
|
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||||
|
Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})},
|
||||||
|
Req: func(req *http.Request) bool {
|
||||||
|
if req.Method != "PUT" {
|
||||||
|
t.Errorf("unexpected method: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
||||||
|
t.Errorf("url doesn't contain name: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if req.URL.Query().Get("namespace") != "bar" {
|
||||||
|
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
client := &FakeRESTClient{
|
||||||
|
Resp: test.Resp,
|
||||||
|
Err: test.HttpErr,
|
||||||
|
}
|
||||||
|
if test.RespFunc != nil {
|
||||||
|
client.Client = test.RespFunc
|
||||||
|
}
|
||||||
|
modifier := &RESTHelper{
|
||||||
|
RESTClient: client,
|
||||||
|
Codec: testapi.Codec(),
|
||||||
|
Versioner: testapi.MetadataAccessor(),
|
||||||
|
}
|
||||||
|
data := []byte{}
|
||||||
|
if test.Object != nil {
|
||||||
|
data = []byte(runtime.EncodeOrDie(testapi.Codec(), test.Object))
|
||||||
|
}
|
||||||
|
err := modifier.Update("bar", "foo", test.Overwrite, data)
|
||||||
|
if (err != nil) != test.Err {
|
||||||
|
t.Errorf("%d: unexpected error: %f %v", i, test.Err, err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.Req != nil && !test.Req(client.Req) {
|
||||||
|
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(client.Req.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%d: unexpected error: %#v", i, err)
|
||||||
|
}
|
||||||
|
t.Logf("got body: %s", string(body))
|
||||||
|
expect := []byte{}
|
||||||
|
if test.ExpectObject != nil {
|
||||||
|
expect = []byte(runtime.EncodeOrDie(testapi.Codec(), test.ExpectObject))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expect, body) {
|
||||||
|
t.Errorf("%d: unexpected body: %s", i, string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue