mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	Refactor Get and Describe to allow extension of types
Get should use ResourceMapper, allow Printer to be abstracted, and extract Describe as *Describer types.
This commit is contained in:
		@@ -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)
 | 
			
		||||
	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)
 | 
			
		||||
	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
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
// 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
 | 
			
		||||
	}
 | 
			
		||||
	pc, err := d.PodClient(namespace)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										83
									
								
								pkg/kubectl/describe_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								pkg/kubectl/describe_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										405
									
								
								pkg/kubectl/resthelper_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										405
									
								
								pkg/kubectl/resthelper_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -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))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user