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() {
|
||||
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) {
|
||||
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 {
|
||||
|
|
|
@ -36,7 +36,7 @@ func (c *FakeServices) List(selector labels.Selector) (*api.ServiceList, error)
|
|||
|
||||
func (c *FakeServices) Get(name string) (*api.Service, error) {
|
||||
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) {
|
||||
|
|
|
@ -35,13 +35,38 @@ import (
|
|||
"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 {
|
||||
Mapper meta.RESTMapper
|
||||
Typer runtime.ObjectTyper
|
||||
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.
|
||||
cmds := &cobra.Command{
|
||||
Use: "kubectl",
|
||||
|
@ -52,15 +77,6 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
|
|||
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.
|
||||
// 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
|
||||
|
@ -78,12 +94,12 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
|
|||
|
||||
cmds.AddCommand(NewCmdVersion(out))
|
||||
cmds.AddCommand(NewCmdProxy(out))
|
||||
cmds.AddCommand(NewCmdGet(out))
|
||||
cmds.AddCommand(NewCmdDescribe(out))
|
||||
|
||||
cmds.AddCommand(factory.NewCmdCreate(out))
|
||||
cmds.AddCommand(factory.NewCmdUpdate(out))
|
||||
cmds.AddCommand(factory.NewCmdDelete(out))
|
||||
cmds.AddCommand(f.NewCmdGet(out))
|
||||
cmds.AddCommand(f.NewCmdDescribe(out))
|
||||
cmds.AddCommand(f.NewCmdCreate(out))
|
||||
cmds.AddCommand(f.NewCmdUpdate(out))
|
||||
cmds.AddCommand(f.NewCmdDelete(out))
|
||||
|
||||
cmds.AddCommand(NewCmdNamespace(out))
|
||||
cmds.AddCommand(NewCmdLog(out))
|
||||
|
|
|
@ -47,7 +47,7 @@ Examples:
|
|||
client, err := f.Client(cmd, mapping)
|
||||
checkErr(err)
|
||||
|
||||
err = kubectl.NewRESTModifier(client, mapping).Create(namespace, data)
|
||||
err = kubectl.NewRESTHelper(client, mapping).Create(namespace, data)
|
||||
checkErr(err)
|
||||
fmt.Fprintf(out, "%s\n", name)
|
||||
},
|
||||
|
|
|
@ -54,7 +54,7 @@ Examples:
|
|||
client, err := f.Client(cmd, mapping)
|
||||
checkErr(err)
|
||||
|
||||
err = kubectl.NewRESTModifier(client, mapping).Delete(namespace, name)
|
||||
err = kubectl.NewRESTHelper(client, mapping).Delete(namespace, name)
|
||||
checkErr(err)
|
||||
fmt.Fprintf(out, "%s\n", name)
|
||||
},
|
||||
|
|
|
@ -17,13 +17,13 @@ limitations under the License.
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdDescribe(out io.Writer) *cobra.Command {
|
||||
func (f *Factory) NewCmdDescribe(out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "describe <resource> <id>",
|
||||
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
|
||||
given resource.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) < 2 {
|
||||
usageError(cmd, "Need to supply a resource and an ID")
|
||||
}
|
||||
resource := args[0]
|
||||
id := args[1]
|
||||
err := kubectl.Describe(out, getKubeClient(cmd), resource, id)
|
||||
mapping, namespace, name := ResourceFromArgs(cmd, args, f.Mapper)
|
||||
|
||||
describer, err := f.Describer(cmd, mapping)
|
||||
checkErr(err)
|
||||
|
||||
s, err := describer.Describe(namespace, name)
|
||||
checkErr(err)
|
||||
fmt.Fprintf(out, "%s\n", s)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
|
|
|
@ -20,12 +20,13 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdGet(out io.Writer) *cobra.Command {
|
||||
func (f *Factory) NewCmdGet(out io.Writer) *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",
|
||||
Long: `Display one or many resources.
|
||||
|
||||
|
@ -44,20 +45,24 @@ Examples:
|
|||
$ kubectl get -f json pod 1234-56-7890-234234-456456
|
||||
<list single pod in json output format>`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var resource, id string
|
||||
if len(args) == 0 {
|
||||
usageError(cmd, "Need to supply a resource.")
|
||||
}
|
||||
if len(args) >= 1 {
|
||||
resource = args[0]
|
||||
}
|
||||
if len(args) >= 2 {
|
||||
id = args[1]
|
||||
}
|
||||
mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, f.Mapper)
|
||||
|
||||
selector := getFlagString(cmd, "selector")
|
||||
labels, err := labels.ParseSelector(selector)
|
||||
checkErr(err)
|
||||
|
||||
client, err := f.Client(cmd, mapping)
|
||||
checkErr(err)
|
||||
|
||||
obj, err := kubectl.NewRESTHelper(client, mapping).Get(namespace, name, labels)
|
||||
checkErr(err)
|
||||
|
||||
outputFormat := getFlagString(cmd, "output")
|
||||
templateFile := getFlagString(cmd, "template")
|
||||
selector := getFlagString(cmd, "selector")
|
||||
err := kubectl.Get(out, getKubeClient(cmd).RESTClient, getKubeNamespace(cmd), resource, id, selector, outputFormat, getFlagBool(cmd, "no-headers"), templateFile)
|
||||
defaultPrinter, err := f.Printer(cmd, mapping, getFlagBool(cmd, "no-headers"))
|
||||
checkErr(err)
|
||||
|
||||
err = kubectl.Print(out, obj, outputFormat, templateFile, defaultPrinter)
|
||||
checkErr(err)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
@ -37,7 +36,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
|
|||
|
||||
if len(args) == 2 {
|
||||
resource := args[0]
|
||||
namespace = api.NamespaceDefault
|
||||
namespace = getKubeNamespace(cmd)
|
||||
name = args[1]
|
||||
if len(name) == 0 || len(resource) == 0 {
|
||||
usageError(cmd, "Must specify filename or command line params")
|
||||
|
@ -63,6 +62,62 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
|
|||
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) {
|
||||
configData, err := readConfigData(filename)
|
||||
checkErr(err)
|
||||
|
|
|
@ -47,7 +47,7 @@ Examples:
|
|||
client, err := f.Client(cmd, mapping)
|
||||
checkErr(err)
|
||||
|
||||
err = kubectl.NewRESTModifier(client, mapping).Update(namespace, name, true, data)
|
||||
err = kubectl.NewRESTHelper(client, mapping).Update(namespace, name, true, data)
|
||||
checkErr(err)
|
||||
fmt.Fprintf(out, "%s\n", name)
|
||||
},
|
||||
|
|
|
@ -18,7 +18,6 @@ package kubectl
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
|
@ -28,35 +27,62 @@ import (
|
|||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
func Describe(w io.Writer, c client.Interface, resource, id string) error {
|
||||
var str string
|
||||
var err error
|
||||
path, err := resolveResource(resolveToPath, resource)
|
||||
// Describer generates output for the named resource or an error
|
||||
// if the output could not be generated.
|
||||
type Describer interface {
|
||||
Describe(namespace, name string) (output string, err error)
|
||||
}
|
||||
|
||||
// Describer returns the default describe functions for each of the standard
|
||||
// Kubernetes types.
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
pc, err := d.PodClient(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(w, str)
|
||||
return err
|
||||
}
|
||||
|
||||
func describePod(w io.Writer, c client.Interface, id string) (string, error) {
|
||||
// TODO this needs proper namespace support
|
||||
pod, err := c.Pods(api.NamespaceDefault).Get(id)
|
||||
pod, err := pc.Get(name)
|
||||
if err != nil {
|
||||
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, "Labels:\t%s\n", formatLabels(pod.Labels))
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
func describeReplicationController(w io.Writer, c client.Interface, id string) (string, error) {
|
||||
// TODO this needs proper namespace support
|
||||
controller, err := c.ReplicationControllers(api.NamespaceDefault).Get(id)
|
||||
// ReplicationControllerDescriber generates information about a replication controller
|
||||
// and the pods it has created.
|
||||
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 {
|
||||
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 {
|
||||
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) {
|
||||
service, err := c.Services(api.NamespaceDefault).Get(id)
|
||||
// ServiceDescriber generates information about a service.
|
||||
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 {
|
||||
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) {
|
||||
minion, err := getMinion(c, id)
|
||||
// MinionDescriber generates information about a minion.
|
||||
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 {
|
||||
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
|
||||
// labels.
|
||||
// TODO Move this to pkg/client and ideally implement it server-side (instead
|
||||
// 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.
|
||||
// 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 {
|
||||
glog.Fatalf("Error getting replication controllers: %v\n", err)
|
||||
}
|
||||
|
@ -171,8 +216,8 @@ func getReplicationControllersForLabels(c client.Interface, labelsToMatch labels
|
|||
return list
|
||||
}
|
||||
|
||||
func getPodStatusForReplicationController(kubeClient client.Interface, controller *api.ReplicationController) (running, waiting, terminated int, err error) {
|
||||
rcPods, err := kubeClient.Pods(controller.Namespace).List(labels.SelectorFromSet(controller.DesiredState.ReplicaSelector))
|
||||
func getPodStatusForReplicationController(c client.PodInterface, controller *api.ReplicationController) (running, waiting, terminated int, err error) {
|
||||
rcPods, err := c.List(labels.SelectorFromSet(controller.DesiredState.ReplicaSelector))
|
||||
if err != nil {
|
||||
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/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||
|
||||
"gopkg.in/v1/yaml"
|
||||
)
|
||||
|
||||
var apiVersionToUse = "v1beta1"
|
||||
|
@ -132,16 +130,6 @@ func promptForString(field string, r io.Reader) string {
|
|||
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.
|
||||
func formatLabels(labelMap map[string]string) string {
|
||||
l := labels.Set(labelMap).String()
|
||||
|
@ -158,90 +146,3 @@ func makeImageList(manifest api.ContainerManifest) string {
|
|||
}
|
||||
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"
|
||||
)
|
||||
|
||||
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
|
||||
switch format {
|
||||
case "json":
|
||||
|
@ -60,7 +73,7 @@ func getPrinter(format, templateFile string, noHeaders bool) (ResourcePrinter, e
|
|||
Template: tmpl,
|
||||
}
|
||||
default:
|
||||
printer = NewHumanReadablePrinter(noHeaders)
|
||||
printer = defaultPrinter
|
||||
}
|
||||
return printer, nil
|
||||
}
|
||||
|
|
|
@ -18,12 +18,13 @@ package kubectl
|
|||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// RESTModifier provides methods for mutating a known or unknown
|
||||
// RESTful resource.
|
||||
type RESTModifier struct {
|
||||
// RESTHelper provides methods for retrieving or mutating a RESTful
|
||||
// resource.
|
||||
type RESTHelper struct {
|
||||
Resource string
|
||||
// A RESTClient capable of mutating this resource
|
||||
RESTClient RESTClient
|
||||
|
@ -34,9 +35,9 @@ type RESTModifier struct {
|
|||
Versioner runtime.ResourceVersioner
|
||||
}
|
||||
|
||||
// NewRESTModifier creates a RESTModifier from a RESTMapping
|
||||
func NewRESTModifier(client RESTClient, mapping *meta.RESTMapping) *RESTModifier {
|
||||
return &RESTModifier{
|
||||
// NewRESTHelper creates a RESTHelper from a ResourceMapping
|
||||
func NewRESTHelper(client RESTClient, mapping *meta.RESTMapping) *RESTHelper {
|
||||
return &RESTHelper{
|
||||
RESTClient: client,
|
||||
Resource: mapping.Resource,
|
||||
Codec: mapping.Codec,
|
||||
|
@ -44,35 +45,39 @@ func NewRESTModifier(client RESTClient, mapping *meta.RESTMapping) *RESTModifier
|
|||
}
|
||||
}
|
||||
|
||||
func (m *RESTModifier) Delete(namespace, name string) error {
|
||||
return m.RESTClient.Delete().Path(m.Resource).Path(name).Do().Error()
|
||||
func (m *RESTHelper) Get(namespace, name string, selector labels.Selector) (runtime.Object, 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 {
|
||||
return m.RESTClient.Post().Path(m.Resource).Body(data).Do().Error()
|
||||
func (m *RESTHelper) Delete(namespace, name string) 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
|
||||
|
||||
obj, err := m.Codec.Decode(data)
|
||||
if err != nil {
|
||||
// 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.
|
||||
version, err := m.Versioner.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
// 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 {
|
||||
// Retrieve the current version of the object to overwrite the server object
|
||||
serverObj, err := c.Get().Path(m.Resource).Path(name).Do().Get()
|
||||
if err != nil {
|
||||
// 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)
|
||||
if err != nil {
|
||||
|
@ -88,5 +93,9 @@ func (m *RESTModifier) Update(namespace, name string, overwrite bool, data []byt
|
|||
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