Merge pull request #5219 from jlowdermilk/kubectl-err-handling

Make kubectl commands return errors and centralize exit handling
This commit is contained in:
Nikhil Jindal 2015-03-11 10:06:52 -07:00
commit 9baa261728
20 changed files with 875 additions and 644 deletions

View File

@ -34,20 +34,25 @@ func (f *Factory) NewCmdClusterInfo(out io.Writer) *cobra.Command {
Short: "Display cluster info",
Long: "Display addresses of the master and services with label kubernetes.io/cluster-service=true",
Run: func(cmd *cobra.Command, args []string) {
RunClusterInfo(f, out, cmd)
err := RunClusterInfo(f, out, cmd)
util.CheckErr(err)
},
}
return cmd
}
func RunClusterInfo(factory *Factory, out io.Writer, cmd *cobra.Command) {
func RunClusterInfo(factory *Factory, out io.Writer, cmd *cobra.Command) error {
client, err := factory.ClientConfig(cmd)
util.CheckErr(err)
if err != nil {
return err
}
fmt.Fprintf(out, "Kubernetes master is running at %v\n", client.Host)
mapper, typer := factory.Object(cmd)
cmdNamespace, err := factory.DefaultNamespace(cmd)
util.CheckErr(err)
if err != nil {
return err
}
// TODO: use generalized labels once they are implemented (#341)
b := resource.NewBuilder(mapper, typer, factory.ClientMapperForCommand(cmd)).
@ -68,6 +73,7 @@ func RunClusterInfo(factory *Factory, out io.Writer, cmd *cobra.Command) {
}
return nil
})
return nil
// TODO: consider printing more information about cluster
}

View File

@ -34,7 +34,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@ -253,7 +252,9 @@ func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMappin
}
if ok {
clientConfig, err := f.ClientConfig(cmd)
cmdutil.CheckErr(err)
if err != nil {
return nil, err
}
defaultVersion := clientConfig.Version
version := cmdutil.OutputVersion(cmd, defaultVersion)
@ -329,12 +330,6 @@ func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
return clientConfig
}
func usageError(cmd *cobra.Command, format string, args ...interface{}) {
glog.Errorf(format, args...)
glog.Errorf("See '%s -h' for help.", cmd.CommandPath())
os.Exit(1)
}
func runHelp(cmd *cobra.Command, args []string) {
cmd.Help()
}

View File

@ -39,54 +39,67 @@ $ cat pod.json | kubectl create -f -`
)
func (f *Factory) NewCmdCreate(out io.Writer) *cobra.Command {
flags := &struct {
Filenames util.StringList
}{}
var filenames util.StringList
cmd := &cobra.Command{
Use: "create -f filename",
Short: "Create a resource by filename or stdin",
Long: create_long,
Example: create_example,
Run: func(cmd *cobra.Command, args []string) {
schema, err := f.Validator(cmd)
err := RunCreate(f, out, cmd, filenames)
cmdutil.CheckErr(err)
cmdNamespace, err := f.DefaultNamespace(cmd)
cmdutil.CheckErr(err)
mapper, typer := f.Object(cmd)
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand(cmd)).
ContinueOnError().
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(flags.Filenames...).
Flatten().
Do()
cmdutil.CheckErr(r.Err())
count := 0
err = r.Visit(func(info *resource.Info) error {
data, err := info.Mapping.Codec.Encode(info.Object)
if err != nil {
return err
}
if err := schema.ValidateBytes(data); err != nil {
return err
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, data)
if err != nil {
return err
}
count++
info.Refresh(obj, true)
fmt.Fprintf(out, "%s\n", info.Name)
return nil
})
cmdutil.CheckErr(err)
if count == 0 {
cmdutil.CheckErr(fmt.Errorf("no objects passed to create"))
}
},
}
cmd.Flags().VarP(&flags.Filenames, "filename", "f", "Filename, directory, or URL to file to use to create the resource")
cmd.Flags().VarP(&filenames, "filename", "f", "Filename, directory, or URL to file to use to create the resource")
return cmd
}
func RunCreate(f *Factory, out io.Writer, cmd *cobra.Command, filenames util.StringList) error {
schema, err := f.Validator(cmd)
if err != nil {
return err
}
cmdNamespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
mapper, typer := f.Object(cmd)
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand(cmd)).
ContinueOnError().
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(filenames...).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
count := 0
err = r.Visit(func(info *resource.Info) error {
data, err := info.Mapping.Codec.Encode(info.Object)
if err != nil {
return err
}
if err := schema.ValidateBytes(data); err != nil {
return err
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, data)
if err != nil {
return err
}
count++
info.Refresh(obj, true)
fmt.Fprintf(out, "%s\n", info.Name)
return nil
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to create")
}
return nil
}

View File

@ -56,46 +56,57 @@ $ kubectl delete pods --all`
)
func (f *Factory) NewCmdDelete(out io.Writer) *cobra.Command {
flags := &struct {
Filenames util.StringList
}{}
var filenames util.StringList
cmd := &cobra.Command{
Use: "delete (-f filename | <resource> (<id> | -l <label> | --all))",
Short: "Delete a resource by filename, stdin, or resource and ID.",
Long: delete_long,
Example: delete_example,
Run: func(cmd *cobra.Command, args []string) {
cmdNamespace, err := f.DefaultNamespace(cmd)
err := RunDelete(f, out, cmd, args, filenames)
cmdutil.CheckErr(err)
mapper, typer := f.Object(cmd)
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand(cmd)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(flags.Filenames...).
SelectorParam(cmdutil.GetFlagString(cmd, "selector")).
SelectAllParam(cmdutil.GetFlagBool(cmd, "all")).
ResourceTypeOrNameArgs(false, args...).
Flatten().
Do()
cmdutil.CheckErr(r.Err())
found := 0
err = r.IgnoreErrors(errors.IsNotFound).Visit(func(r *resource.Info) error {
found++
if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
return err
}
fmt.Fprintf(out, "%s\n", r.Name)
return nil
})
cmdutil.CheckErr(err)
if found == 0 {
fmt.Fprintf(cmd.Out(), "No resources found\n")
}
},
}
cmd.Flags().VarP(&flags.Filenames, "filename", "f", "Filename, directory, or URL to a file containing the resource to delete")
cmd.Flags().VarP(&filenames, "filename", "f", "Filename, directory, or URL to a file containing the resource to delete")
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on")
cmd.Flags().Bool("all", false, "[-all] to select all the specified resources")
return cmd
}
func RunDelete(f *Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error {
cmdNamespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
mapper, typer := f.Object(cmd)
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand(cmd)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(filenames...).
SelectorParam(cmdutil.GetFlagString(cmd, "selector")).
SelectAllParam(cmdutil.GetFlagBool(cmd, "all")).
ResourceTypeOrNameArgs(false, args...).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
found := 0
err = r.IgnoreErrors(errors.IsNotFound).Visit(func(r *resource.Info) error {
found++
if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
return err
}
fmt.Fprintf(out, "%s\n", r.Name)
return nil
})
if err != nil {
return err
}
if found == 0 {
fmt.Fprintf(cmd.Out(), "No resources found\n")
}
return nil
}

View File

@ -33,20 +33,35 @@ func (f *Factory) 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) {
cmdNamespace, err := f.DefaultNamespace(cmd)
err := RunDescribe(f, out, cmd, args)
util.CheckErr(err)
mapper, _ := f.Object(cmd)
// TODO: use resource.Builder instead
mapping, namespace, name := util.ResourceFromArgs(cmd, args, mapper, cmdNamespace)
describer, err := f.Describer(cmd, mapping)
util.CheckErr(err)
s, err := describer.Describe(namespace, name)
util.CheckErr(err)
fmt.Fprintf(out, "%s\n", s)
},
}
return cmd
}
func RunDescribe(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
cmdNamespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
mapper, _ := f.Object(cmd)
// TODO: use resource.Builder instead
mapping, namespace, name, err := util.ResourceFromArgs(cmd, args, mapper, cmdNamespace)
if err != nil {
return err
}
describer, err := f.Describer(cmd, mapping)
if err != nil {
return err
}
s, err := describer.Describe(namespace, name)
if err != nil {
return err
}
fmt.Fprintf(out, "%s\n", s)
return nil
}

View File

@ -39,97 +39,105 @@ $ kubectl exec -p 123456-7890 -c ruby-container -i -t -- bash -il`
)
func (f *Factory) NewCmdExec(cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command {
flags := &struct {
pod string
container string
stdin bool
tty bool
}{}
cmd := &cobra.Command{
Use: "exec -p <pod> -c <container> -- <command> [<args...>]",
Short: "Execute a command in a container.",
Long: "Execute a command in a container.",
Example: exec_example,
Run: func(cmd *cobra.Command, args []string) {
if len(flags.pod) == 0 {
usageError(cmd, "<pod> is required for exec")
}
if len(args) < 1 {
usageError(cmd, "<command> is required for exec")
}
namespace, err := f.DefaultNamespace(cmd)
util.CheckErr(err)
client, err := f.Client(cmd)
util.CheckErr(err)
pod, err := client.Pods(namespace).Get(flags.pod)
util.CheckErr(err)
if pod.Status.Phase != api.PodRunning {
glog.Fatalf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase)
}
if len(flags.container) == 0 {
flags.container = pod.Spec.Containers[0].Name
}
var stdin io.Reader
if util.GetFlagBool(cmd, "stdin") {
stdin = cmdIn
if flags.tty {
if file, ok := cmdIn.(*os.File); ok {
inFd := file.Fd()
if term.IsTerminal(inFd) {
oldState, err := term.SetRawTerminal(inFd)
if err != nil {
glog.Fatal(err)
}
// this handles a clean exit, where the command finished
defer term.RestoreTerminal(inFd, oldState)
// SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens
// for SIGINT and restores the terminal before exiting)
// this handles SIGTERM
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM)
go func() {
<-sigChan
term.RestoreTerminal(inFd, oldState)
os.Exit(0)
}()
} else {
glog.Warning("Stdin is not a terminal")
}
} else {
flags.tty = false
glog.Warning("Unable to use a TTY")
}
}
}
config, err := f.ClientConfig(cmd)
util.CheckErr(err)
req := client.RESTClient.Get().
Prefix("proxy").
Resource("minions").
Name(pod.Status.Host).
Suffix("exec", namespace, flags.pod, flags.container)
e := remotecommand.New(req, config, args, stdin, cmdOut, cmdErr, flags.tty)
err = e.Execute()
err := RunExec(f, cmdIn, cmdOut, cmdErr, cmd, args)
util.CheckErr(err)
},
}
cmd.Flags().StringVarP(&flags.pod, "pod", "p", "", "Pod name")
cmd.Flags().StringP("pod", "p", "", "Pod name")
// TODO support UID
cmd.Flags().StringVarP(&flags.container, "container", "c", "", "Container name")
cmd.Flags().BoolVarP(&flags.stdin, "stdin", "i", false, "Pass stdin to the container")
cmd.Flags().BoolVarP(&flags.tty, "tty", "t", false, "Stdin is a TTY")
cmd.Flags().StringP("container", "c", "", "Container name")
cmd.Flags().BoolP("stdin", "i", false, "Pass stdin to the container")
cmd.Flags().BoolP("tty", "t", false, "Stdin is a TTY")
return cmd
}
func RunExec(f *Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobra.Command, args []string) error {
podName := util.GetFlagString(cmd, "pod")
if len(podName) == 0 {
return util.UsageError(cmd, "<pod> is required for exec")
}
if len(args) < 1 {
return util.UsageError(cmd, "<command> is required for exec")
}
namespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
client, err := f.Client(cmd)
if err != nil {
return err
}
pod, err := client.Pods(namespace).Get(podName)
if err != nil {
return err
}
if pod.Status.Phase != api.PodRunning {
glog.Fatalf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase)
}
containerName := util.GetFlagString(cmd, "container")
if len(containerName) == 0 {
containerName = pod.Spec.Containers[0].Name
}
var stdin io.Reader
tty := util.GetFlagBool(cmd, "tty")
if util.GetFlagBool(cmd, "stdin") {
stdin = cmdIn
if tty {
if file, ok := cmdIn.(*os.File); ok {
inFd := file.Fd()
if term.IsTerminal(inFd) {
oldState, err := term.SetRawTerminal(inFd)
if err != nil {
glog.Fatal(err)
}
// this handles a clean exit, where the command finished
defer term.RestoreTerminal(inFd, oldState)
// SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens
// for SIGINT and restores the terminal before exiting)
// this handles SIGTERM
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM)
go func() {
<-sigChan
term.RestoreTerminal(inFd, oldState)
os.Exit(0)
}()
} else {
glog.Warning("Stdin is not a terminal")
}
} else {
tty = false
glog.Warning("Unable to use a TTY")
}
}
}
config, err := f.ClientConfig(cmd)
if err != nil {
return err
}
req := client.RESTClient.Get().
Prefix("proxy").
Resource("minions").
Name(pod.Status.Host).
Suffix("exec", namespace, podName, containerName)
e := remotecommand.New(req, config, args, stdin, cmdOut, cmdErr, tty)
return e.Execute()
}

View File

@ -46,59 +46,7 @@ func (f *Factory) NewCmdExposeService(out io.Writer) *cobra.Command {
Long: expose_long,
Example: expose_example,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
usageError(cmd, "<name> is required for expose")
}
namespace, err := f.DefaultNamespace(cmd)
util.CheckErr(err)
client, err := f.Client(cmd)
util.CheckErr(err)
generatorName := util.GetFlagString(cmd, "generator")
generator, found := kubectl.Generators[generatorName]
if !found {
usageError(cmd, fmt.Sprintf("Generator: %s not found.", generator))
}
if util.GetFlagInt(cmd, "port") < 1 {
usageError(cmd, "--port is required and must be a positive integer.")
}
names := generator.ParamNames()
params := kubectl.MakeParams(cmd, names)
if len(util.GetFlagString(cmd, "service-name")) == 0 {
params["name"] = args[0]
} else {
params["name"] = util.GetFlagString(cmd, "service-name")
}
if _, found := params["selector"]; !found {
rc, err := client.ReplicationControllers(namespace).Get(args[0])
util.CheckErr(err)
params["selector"] = kubectl.MakeLabels(rc.Spec.Selector)
}
if util.GetFlagBool(cmd, "create-external-load-balancer") {
params["create-external-load-balancer"] = "true"
}
err = kubectl.ValidateParams(names, params)
util.CheckErr(err)
service, err := generator.Generate(params)
util.CheckErr(err)
inline := util.GetFlagString(cmd, "overrides")
if len(inline) > 0 {
service, err = util.Merge(service, inline, "Service")
util.CheckErr(err)
}
// TODO: extract this flag to a central location, when such a location exists.
if !util.GetFlagBool(cmd, "dry-run") {
service, err = client.Services(namespace).Create(service.(*api.Service))
util.CheckErr(err)
}
err = f.PrintObject(cmd, service, out)
err := RunExpose(f, out, cmd, args)
util.CheckErr(err)
},
}
@ -115,3 +63,73 @@ func (f *Factory) NewCmdExposeService(out io.Writer) *cobra.Command {
cmd.Flags().String("service-name", "", "The name for the newly created service.")
return cmd
}
func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return util.UsageError(cmd, "<name> is required for expose")
}
namespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
client, err := f.Client(cmd)
if err != nil {
return err
}
generatorName := util.GetFlagString(cmd, "generator")
generator, found := kubectl.Generators[generatorName]
if !found {
return util.UsageError(cmd, fmt.Sprintf("Generator: %s not found.", generator))
}
if util.GetFlagInt(cmd, "port") < 1 {
return util.UsageError(cmd, "--port is required and must be a positive integer.")
}
names := generator.ParamNames()
params := kubectl.MakeParams(cmd, names)
if len(util.GetFlagString(cmd, "service-name")) == 0 {
params["name"] = args[0]
} else {
params["name"] = util.GetFlagString(cmd, "service-name")
}
if _, found := params["selector"]; !found {
rc, err := client.ReplicationControllers(namespace).Get(args[0])
if err != nil {
return err
}
params["selector"] = kubectl.MakeLabels(rc.Spec.Selector)
}
if util.GetFlagBool(cmd, "create-external-load-balancer") {
params["create-external-load-balancer"] = "true"
}
err = kubectl.ValidateParams(names, params)
if err != nil {
return err
}
service, err := generator.Generate(params)
if err != nil {
return err
}
inline := util.GetFlagString(cmd, "overrides")
if len(inline) > 0 {
service, err = util.Merge(service, inline, "Service")
if err != nil {
return err
}
}
// TODO: extract this flag to a central location, when such a location exists.
if !util.GetFlagBool(cmd, "dry-run") {
service, err = client.Services(namespace).Create(service.(*api.Service))
if err != nil {
return err
}
}
return f.PrintObject(cmd, service, out)
}

View File

@ -63,7 +63,8 @@ func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command {
Long: get_long,
Example: get_example,
Run: func(cmd *cobra.Command, args []string) {
RunGet(f, out, cmd, args)
err := RunGet(f, out, cmd, args)
util.CheckErr(err)
},
}
util.AddPrinterFlags(cmd)
@ -75,13 +76,14 @@ func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command {
// RunGet implements the generic Get command
// TODO: convert all direct flag accessors to a struct and pass that instead of cmd
// TODO: return an error instead of using glog.Fatal and checkErr
func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) {
func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
selector := util.GetFlagString(cmd, "selector")
mapper, typer := f.Object(cmd)
cmdNamespace, err := f.DefaultNamespace(cmd)
util.CheckErr(err)
if err != nil {
return err
}
// handle watch separately since we cannot watch multiple resource types
isWatch, isWatchOnly := util.GetFlagBool(cmd, "watch"), util.GetFlagBool(cmd, "watch-only")
@ -92,35 +94,47 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) {
ResourceTypeOrNameArgs(true, args...).
SingleResourceType().
Do()
util.CheckErr(r.Err())
if err != nil {
return err
}
mapping, err := r.ResourceMapping()
util.CheckErr(err)
if err != nil {
return err
}
printer, err := f.PrinterForMapping(cmd, mapping)
util.CheckErr(err)
if err != nil {
return err
}
obj, err := r.Object()
util.CheckErr(err)
if err != nil {
return err
}
rv, err := mapping.MetadataAccessor.ResourceVersion(obj)
util.CheckErr(err)
if err != nil {
return err
}
// print the current object
if !isWatchOnly {
if err := printer.PrintObj(obj, out); err != nil {
util.CheckErr(fmt.Errorf("unable to output the provided object: %v", err))
return fmt.Errorf("unable to output the provided object: %v", err)
}
}
// print watched changes
w, err := r.Watch(rv)
util.CheckErr(err)
if err != nil {
return err
}
kubectl.WatchLoop(w, func(e watch.Event) error {
return printer.PrintObj(e.Object, out)
})
return
return nil
}
b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand(cmd)).
@ -129,11 +143,15 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) {
ResourceTypeOrNameArgs(true, args...).
Latest()
printer, generic, err := util.PrinterForCommand(cmd)
util.CheckErr(err)
if err != nil {
return err
}
if generic {
clientConfig, err := f.ClientConfig(cmd)
util.CheckErr(err)
if err != nil {
return err
}
defaultVersion := clientConfig.Version
// the outermost object will be converted to the output-version
@ -141,7 +159,9 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) {
r := b.Flatten().Do()
obj, err := r.Object()
util.CheckErr(err)
if err != nil {
return err
}
// try conversion to all the possible versions
// TODO: simplify by adding a ResourceBuilder mode
@ -157,18 +177,15 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) {
// builder on initialization
printer := kubectl.NewVersionedPrinter(printer, api.Scheme, versions...)
err = printer.PrintObj(obj, out)
util.CheckErr(err)
return
return printer.PrintObj(obj, out)
}
// use the default printer for each object
err = b.Do().Visit(func(r *resource.Info) error {
return b.Do().Visit(func(r *resource.Info) error {
printer, err := f.PrinterForMapping(cmd, r.Mapping)
if err != nil {
return err
}
return printer.PrintObj(r.Object, out)
})
util.CheckErr(err)
}

View File

@ -55,38 +55,8 @@ func (f *Factory) NewCmdLabel(out io.Writer) *cobra.Command {
Long: label_long,
Example: label_example,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 2 {
usageError(cmd, "<resource> <name> is required")
}
if len(args) < 3 {
usageError(cmd, "at least one label update is required.")
}
res := args[:2]
cmdNamespace, err := f.DefaultNamespace(cmd)
err := RunLabel(f, out, cmd, args)
util.CheckErr(err)
mapper, _ := f.Object(cmd)
// TODO: use resource.Builder instead
mapping, namespace, name := util.ResourceFromArgs(cmd, res, mapper, cmdNamespace)
client, err := f.RESTClient(cmd, mapping)
util.CheckErr(err)
labels, remove, err := parseLabels(args[2:])
util.CheckErr(err)
overwrite := util.GetFlagBool(cmd, "overwrite")
resourceVersion := util.GetFlagString(cmd, "resource-version")
obj, err := updateObject(client, mapping, namespace, name, func(obj runtime.Object) runtime.Object {
outObj, err := labelFunc(obj, overwrite, resourceVersion, labels, remove)
util.CheckErr(err)
return outObj
})
util.CheckErr(err)
printer, err := f.PrinterForMapping(cmd, mapping)
util.CheckErr(err)
printer.PrintObj(obj, out)
},
}
util.AddPrinterFlags(cmd)
@ -95,7 +65,7 @@ func (f *Factory) NewCmdLabel(out io.Writer) *cobra.Command {
return cmd
}
func updateObject(client resource.RESTClient, mapping *meta.RESTMapping, namespace, name string, updateFn func(runtime.Object) runtime.Object) (runtime.Object, error) {
func updateObject(client resource.RESTClient, mapping *meta.RESTMapping, namespace, name string, updateFn func(runtime.Object) (runtime.Object, error)) (runtime.Object, error) {
helper := resource.NewHelper(client, mapping)
obj, err := helper.Get(namespace, name)
@ -103,7 +73,10 @@ func updateObject(client resource.RESTClient, mapping *meta.RESTMapping, namespa
return nil, err
}
obj = updateFn(obj)
obj, err = updateFn(obj)
if err != nil {
return nil, err
}
data, err := helper.Codec.Encode(obj)
if err != nil {
@ -177,3 +150,54 @@ func labelFunc(obj runtime.Object, overwrite bool, resourceVersion string, label
}
return obj, nil
}
func RunLabel(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return util.UsageError(cmd, "<resource> <name> is required")
}
if len(args) < 3 {
return util.UsageError(cmd, "at least one label update is required.")
}
res := args[:2]
cmdNamespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
mapper, _ := f.Object(cmd)
// TODO: use resource.Builder instead
mapping, namespace, name, err := util.ResourceFromArgs(cmd, res, mapper, cmdNamespace)
if err != nil {
return err
}
client, err := f.RESTClient(cmd, mapping)
if err != nil {
return err
}
labels, remove, err := parseLabels(args[2:])
if err != nil {
return err
}
overwrite := util.GetFlagBool(cmd, "overwrite")
resourceVersion := util.GetFlagString(cmd, "resource-version")
obj, err := updateObject(client, mapping, namespace, name, func(obj runtime.Object) (runtime.Object, error) {
outObj, err := labelFunc(obj, overwrite, resourceVersion, labels, remove)
if err != nil {
return nil, err
}
return outObj, nil
})
if err != nil {
return err
}
printer, err := f.PrinterForMapping(cmd, mapping)
if err != nil {
return err
}
printer.PrintObj(obj, out)
return nil
}

View File

@ -19,7 +19,6 @@ package cmd
import (
"fmt"
"io"
"os"
"strconv"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -65,56 +64,7 @@ func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
Long: "Print the logs for a container in a pod. If the pod has only one container, the container name is optional.",
Example: log_example,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
usageError(cmd, "<pod> is required for log")
}
if len(args) > 2 {
usageError(cmd, "log <pod> [<container>]")
}
namespace, err := f.DefaultNamespace(cmd)
util.CheckErr(err)
client, err := f.Client(cmd)
util.CheckErr(err)
podID := args[0]
pod, err := client.Pods(namespace).Get(podID)
util.CheckErr(err)
var container string
if len(args) == 1 {
if len(pod.Spec.Containers) != 1 {
if !util.GetFlagBool(cmd, "interactive") {
usageError(cmd, "<container> is required for pods with multiple containers")
} else {
container = selectContainer(pod, os.Stdin, out)
}
} else {
// Get logs for the only container in the pod
container = pod.Spec.Containers[0].Name
}
} else {
container = args[1]
}
follow := false
if util.GetFlagBool(cmd, "follow") {
follow = true
}
readCloser, err := client.RESTClient.Get().
Prefix("proxy").
Resource("minions").
Name(pod.Status.Host).
Suffix("containerLogs", namespace, podID, container).
Param("follow", strconv.FormatBool(follow)).
Stream()
util.CheckErr(err)
defer readCloser.Close()
_, err = io.Copy(out, readCloser)
err := RunLog(f, out, cmd, args)
util.CheckErr(err)
},
}
@ -122,3 +72,55 @@ func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
cmd.Flags().Bool("interactive", true, "If true, prompt the user for input when required. Default true.")
return cmd
}
func RunLog(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return util.UsageError(cmd, "<pod> is required for log")
}
if len(args) > 2 {
return util.UsageError(cmd, "log <pod> [<container>]")
}
namespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
client, err := f.Client(cmd)
if err != nil {
return err
}
podID := args[0]
pod, err := client.Pods(namespace).Get(podID)
if err != nil {
return err
}
var container string
if len(args) == 1 {
} else {
container = args[1]
}
follow := false
if util.GetFlagBool(cmd, "follow") {
follow = true
}
readCloser, err := client.RESTClient.Get().
Prefix("proxy").
Resource("minions").
Name(pod.Status.Host).
Suffix("containerLogs", namespace, podID, container).
Param("follow", strconv.FormatBool(follow)).
Stream()
if err != nil {
return err
}
defer readCloser.Close()
_, err = io.Copy(out, readCloser)
return err
}

View File

@ -17,10 +17,10 @@ limitations under the License.
package cmd
import (
"fmt"
"io"
"os"
"github.com/golang/glog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
"github.com/spf13/cobra"
)
@ -34,8 +34,7 @@ func NewCmdNamespace(out io.Writer) *cobra.Command {
namespace has been superceded by the context.namespace field of .kubeconfig files. See 'kubectl config set-context --help' for more details.
`,
Run: func(cmd *cobra.Command, args []string) {
glog.Errorln("namespace has been superceded by the context.namespace field of .kubeconfig files. See 'kubectl config set-context --help' for more details.")
os.Exit(1)
util.CheckErr(fmt.Errorf("namespace has been superceded by the context.namespace field of .kubeconfig files. See 'kubectl config set-context --help' for more details."))
},
}
return cmd

View File

@ -42,65 +42,75 @@ $ kubectl port-forward -p mypod 0:5000
)
func (f *Factory) NewCmdPortForward() *cobra.Command {
flags := &struct {
pod string
container string
}{}
cmd := &cobra.Command{
Use: "port-forward -p <pod> [<local port>:]<remote port> [<port>...]",
Short: "Forward 1 or more local ports to a pod.",
Long: "Forward 1 or more local ports to a pod.",
Example: portforward_example,
Run: func(cmd *cobra.Command, args []string) {
if len(flags.pod) == 0 {
usageError(cmd, "<pod> is required for exec")
}
if len(args) < 1 {
usageError(cmd, "at least 1 <port> is required for port-forward")
}
namespace, err := f.DefaultNamespace(cmd)
util.CheckErr(err)
client, err := f.Client(cmd)
util.CheckErr(err)
pod, err := client.Pods(namespace).Get(flags.pod)
util.CheckErr(err)
if pod.Status.Phase != api.PodRunning {
glog.Fatalf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase)
}
config, err := f.ClientConfig(cmd)
util.CheckErr(err)
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
defer signal.Stop(signals)
stopCh := make(chan struct{}, 1)
go func() {
<-signals
close(stopCh)
}()
req := client.RESTClient.Get().
Prefix("proxy").
Resource("minions").
Name(pod.Status.Host).
Suffix("portForward", namespace, flags.pod)
pf, err := portforward.New(req, config, args, stopCh)
util.CheckErr(err)
err = pf.ForwardPorts()
err := RunPortForward(f, cmd, args)
util.CheckErr(err)
},
}
cmd.Flags().StringVarP(&flags.pod, "pod", "p", "", "Pod name")
cmd.Flags().StringP("pod", "p", "", "Pod name")
// TODO support UID
return cmd
}
func RunPortForward(f *Factory, cmd *cobra.Command, args []string) error {
podName := util.GetFlagString(cmd, "pod")
if len(podName) == 0 {
return util.UsageError(cmd, "<pod> is required for exec")
}
if len(args) < 1 {
return util.UsageError(cmd, "at least 1 <port> is required for port-forward")
}
namespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
client, err := f.Client(cmd)
if err != nil {
return err
}
pod, err := client.Pods(namespace).Get(podName)
if err != nil {
return err
}
if pod.Status.Phase != api.PodRunning {
glog.Fatalf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase)
}
config, err := f.ClientConfig(cmd)
if err != nil {
return err
}
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
defer signal.Stop(signals)
stopCh := make(chan struct{}, 1)
go func() {
<-signals
close(stopCh)
}()
req := client.RESTClient.Get().
Prefix("proxy").
Resource("minions").
Name(pod.Status.Host).
Suffix("portForward", namespace, podName)
pf, err := portforward.New(req, config, args, stopCh)
if err != nil {
return err
}
return pf.ForwardPorts()
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package cmd
import (
"fmt"
"io"
"strings"
@ -32,25 +33,8 @@ func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command {
Short: "Run a proxy to the Kubernetes API server",
Long: `Run a proxy to the Kubernetes API server.`,
Run: func(cmd *cobra.Command, args []string) {
port := util.GetFlagInt(cmd, "port")
glog.Infof("Starting to serve on localhost:%d", port)
clientConfig, err := f.ClientConfig(cmd)
err := RunProxy(f, out, cmd)
util.CheckErr(err)
staticPrefix := util.GetFlagString(cmd, "www-prefix")
if !strings.HasSuffix(staticPrefix, "/") {
staticPrefix += "/"
}
apiProxyPrefix := util.GetFlagString(cmd, "api-prefix")
if !strings.HasSuffix(apiProxyPrefix, "/") {
apiProxyPrefix += "/"
}
server, err := kubectl.NewProxyServer(util.GetFlagString(cmd, "www"), apiProxyPrefix, staticPrefix, clientConfig)
util.CheckErr(err)
glog.Fatal(server.Serve(port))
},
}
cmd.Flags().StringP("www", "w", "", "Also serve static files from the given directory under the specified prefix.")
@ -59,3 +43,30 @@ func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command {
cmd.Flags().IntP("port", "p", 8001, "The port on which to run the proxy.")
return cmd
}
func RunProxy(f *Factory, out io.Writer, cmd *cobra.Command) error {
port := util.GetFlagInt(cmd, "port")
fmt.Fprintf(out, "Starting to serve on localhost:%d", port)
clientConfig, err := f.ClientConfig(cmd)
if err != nil {
return err
}
staticPrefix := util.GetFlagString(cmd, "www-prefix")
if !strings.HasSuffix(staticPrefix, "/") {
staticPrefix += "/"
}
apiProxyPrefix := util.GetFlagString(cmd, "api-prefix")
if !strings.HasSuffix(apiProxyPrefix, "/") {
apiProxyPrefix += "/"
}
server, err := kubectl.NewProxyServer(util.GetFlagString(cmd, "www"), apiProxyPrefix, staticPrefix, clientConfig)
if err != nil {
return err
}
glog.Fatal(server.Serve(port))
return nil
}

View File

@ -52,32 +52,8 @@ func (f *Factory) NewCmdResize(out io.Writer) *cobra.Command {
Long: resize_long,
Example: resize_example,
Run: func(cmd *cobra.Command, args []string) {
count := util.GetFlagInt(cmd, "replicas")
if len(args) != 2 || count < 0 {
usageError(cmd, "--replicas=<count> <resource> <id>")
}
cmdNamespace, err := f.DefaultNamespace(cmd)
err := RunResize(f, out, cmd, args)
util.CheckErr(err)
mapper, _ := f.Object(cmd)
// TODO: use resource.Builder instead
mapping, namespace, name := util.ResourceFromArgs(cmd, args, mapper, cmdNamespace)
resizer, err := f.Resizer(cmd, mapping)
util.CheckErr(err)
resourceVersion := util.GetFlagString(cmd, "resource-version")
currentSize := util.GetFlagInt(cmd, "current-replicas")
precondition := &kubectl.ResizePrecondition{currentSize, resourceVersion}
cond := kubectl.ResizeCondition(resizer, precondition, namespace, name, uint(count))
msg := "resized"
if err = wait.Poll(retryFrequency, retryTimeout, cond); err != nil {
msg = fmt.Sprintf("Failed to resize controller in spite of retrying for %s", retryTimeout)
util.CheckErr(err)
}
fmt.Fprintf(out, "%s\n", msg)
},
}
cmd.Flags().String("resource-version", "", "Precondition for resource version. Requires that the current resource version match this value in order to resize.")
@ -85,3 +61,42 @@ func (f *Factory) NewCmdResize(out io.Writer) *cobra.Command {
cmd.Flags().Int("replicas", -1, "The new desired number of replicas. Required.")
return cmd
}
func RunResize(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
count := util.GetFlagInt(cmd, "replicas")
if len(args) != 2 || count < 0 {
return util.UsageError(cmd, "--replicas=<count> <resource> <id>")
}
cmdNamespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
mapper, _ := f.Object(cmd)
// TODO: use resource.Builder instead
mapping, namespace, name, err := util.ResourceFromArgs(cmd, args, mapper, cmdNamespace)
if err != nil {
return err
}
resizer, err := f.Resizer(cmd, mapping)
if err != nil {
return err
}
resourceVersion := util.GetFlagString(cmd, "resource-version")
currentSize := util.GetFlagInt(cmd, "current-replicas")
precondition := &kubectl.ResizePrecondition{currentSize, resourceVersion}
cond := kubectl.ResizeCondition(resizer, precondition, namespace, name, uint(count))
msg := "resized"
if err = wait.Poll(retryFrequency, retryTimeout, cond); err != nil {
msg = fmt.Sprintf("Failed to resize controller in spite of retrying for %s", retryTimeout)
if err != nil {
return err
}
}
fmt.Fprintf(out, "%s\n", msg)
return nil
}

View File

@ -49,73 +49,8 @@ func (f *Factory) NewCmdRollingUpdate(out io.Writer) *cobra.Command {
Long: rollingupdate_long,
Example: rollingupdate_example,
Run: func(cmd *cobra.Command, args []string) {
filename := util.GetFlagString(cmd, "filename")
if len(filename) == 0 {
usageError(cmd, "Must specify filename for new controller")
}
period := util.GetFlagDuration(cmd, "update-period")
interval := util.GetFlagDuration(cmd, "poll-interval")
timeout := util.GetFlagDuration(cmd, "timeout")
if len(args) != 1 {
usageError(cmd, "Must specify the controller to update")
}
oldName := args[0]
schema, err := f.Validator(cmd)
err := RunRollingUpdate(f, out, cmd, args)
util.CheckErr(err)
clientConfig, err := f.ClientConfig(cmd)
util.CheckErr(err)
cmdApiVersion := clientConfig.Version
mapper, typer := f.Object(cmd)
// TODO: use resource.Builder instead
mapping, namespace, newName, data := util.ResourceFromFile(filename, typer, mapper, schema, cmdApiVersion)
if mapping.Kind != "ReplicationController" {
usageError(cmd, "%s does not specify a valid ReplicationController", filename)
}
if oldName == newName {
usageError(cmd, "%s cannot have the same name as the existing ReplicationController %s",
filename, oldName)
}
cmdNamespace, err := f.DefaultNamespace(cmd)
util.CheckErr(err)
// TODO: use resource.Builder instead
err = util.CompareNamespace(cmdNamespace, namespace)
util.CheckErr(err)
client, err := f.Client(cmd)
util.CheckErr(err)
obj, err := mapping.Codec.Decode(data)
util.CheckErr(err)
newRc := obj.(*api.ReplicationController)
updater := kubectl.NewRollingUpdater(cmdNamespace, client)
// fetch rc
oldRc, err := client.ReplicationControllers(cmdNamespace).Get(oldName)
util.CheckErr(err)
var hasLabel bool
for key, oldValue := range oldRc.Spec.Selector {
if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
hasLabel = true
break
}
}
if !hasLabel {
usageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s",
filename, oldName)
}
// TODO: handle resizes during rolling update
if newRc.Spec.Replicas == 0 {
newRc.Spec.Replicas = oldRc.Spec.Replicas
}
err = updater.Update(out, oldRc, newRc, period, interval, timeout)
util.CheckErr(err)
fmt.Fprintf(out, "%s\n", newName)
},
}
cmd.Flags().String("update-period", updatePeriod, `Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
@ -124,3 +59,93 @@ func (f *Factory) NewCmdRollingUpdate(out io.Writer) *cobra.Command {
cmd.Flags().StringP("filename", "f", "", "Filename or URL to file to use to create the new controller.")
return cmd
}
func RunRollingUpdate(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
filename := util.GetFlagString(cmd, "filename")
if len(filename) == 0 {
return util.UsageError(cmd, "Must specify filename for new controller")
}
period := util.GetFlagDuration(cmd, "update-period")
interval := util.GetFlagDuration(cmd, "poll-interval")
timeout := util.GetFlagDuration(cmd, "timeout")
if len(args) != 1 {
return util.UsageError(cmd, "Must specify the controller to update")
}
oldName := args[0]
schema, err := f.Validator(cmd)
if err != nil {
return err
}
clientConfig, err := f.ClientConfig(cmd)
if err != nil {
return err
}
cmdApiVersion := clientConfig.Version
mapper, typer := f.Object(cmd)
// TODO: use resource.Builder instead
mapping, namespace, newName, data, err := util.ResourceFromFile(filename, typer, mapper, schema, cmdApiVersion)
if err != nil {
return err
}
if mapping.Kind != "ReplicationController" {
return util.UsageError(cmd, "%s does not specify a valid ReplicationController", filename)
}
if oldName == newName {
return util.UsageError(cmd, "%s cannot have the same name as the existing ReplicationController %s",
filename, oldName)
}
cmdNamespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
// TODO: use resource.Builder instead
err = util.CompareNamespace(cmdNamespace, namespace)
if err != nil {
return err
}
client, err := f.Client(cmd)
if err != nil {
return err
}
obj, err := mapping.Codec.Decode(data)
if err != nil {
return err
}
newRc := obj.(*api.ReplicationController)
updater := kubectl.NewRollingUpdater(cmdNamespace, client)
// fetch rc
oldRc, err := client.ReplicationControllers(cmdNamespace).Get(oldName)
if err != nil {
return err
}
var hasLabel bool
for key, oldValue := range oldRc.Spec.Selector {
if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
hasLabel = true
break
}
}
if !hasLabel {
return util.UsageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s",
filename, oldName)
}
// TODO: handle resizes during rolling update
if newRc.Spec.Replicas == 0 {
newRc.Spec.Replicas = oldRc.Spec.Replicas
}
err = updater.Update(out, oldRc, newRc, period, interval, timeout)
if err != nil {
return err
}
fmt.Fprintf(out, "%s\n", newName)
return nil
}

View File

@ -49,44 +49,7 @@ func (f *Factory) NewCmdRunContainer(out io.Writer) *cobra.Command {
Long: run_long,
Example: run_example,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
usageError(cmd, "<name> is required for run-container")
}
namespace, err := f.DefaultNamespace(cmd)
util.CheckErr(err)
client, err := f.Client(cmd)
util.CheckErr(err)
generatorName := util.GetFlagString(cmd, "generator")
generator, found := kubectl.Generators[generatorName]
if !found {
usageError(cmd, fmt.Sprintf("Generator: %s not found.", generator))
}
names := generator.ParamNames()
params := kubectl.MakeParams(cmd, names)
params["name"] = args[0]
err = kubectl.ValidateParams(names, params)
util.CheckErr(err)
controller, err := generator.Generate(params)
util.CheckErr(err)
inline := util.GetFlagString(cmd, "overrides")
if len(inline) > 0 {
controller, err = util.Merge(controller, inline, "ReplicationController")
util.CheckErr(err)
}
// TODO: extract this flag to a central location, when such a location exists.
if !util.GetFlagBool(cmd, "dry-run") {
controller, err = client.ReplicationControllers(namespace).Create(controller.(*api.ReplicationController))
util.CheckErr(err)
}
err = f.PrintObject(cmd, controller, out)
err := RunRunContainer(f, out, cmd, args)
util.CheckErr(err)
},
}
@ -100,3 +63,56 @@ func (f *Factory) NewCmdRunContainer(out io.Writer) *cobra.Command {
cmd.Flags().StringP("labels", "l", "", "Labels to apply to the pod(s) created by this call to run-container.")
return cmd
}
func RunRunContainer(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return util.UsageError(cmd, "<name> is required for run-container")
}
namespace, err := f.DefaultNamespace(cmd)
if err != nil {
return err
}
client, err := f.Client(cmd)
if err != nil {
return err
}
generatorName := util.GetFlagString(cmd, "generator")
generator, found := kubectl.Generators[generatorName]
if !found {
return util.UsageError(cmd, fmt.Sprintf("Generator: %s not found.", generator))
}
names := generator.ParamNames()
params := kubectl.MakeParams(cmd, names)
params["name"] = args[0]
err = kubectl.ValidateParams(names, params)
if err != nil {
return err
}
controller, err := generator.Generate(params)
if err != nil {
return err
}
inline := util.GetFlagString(cmd, "overrides")
if len(inline) > 0 {
controller, err = util.Merge(controller, inline, "ReplicationController")
if err != nil {
return err
}
}
// TODO: extract this flag to a central location, when such a location exists.
if !util.GetFlagBool(cmd, "dry-run") {
controller, err = client.ReplicationControllers(namespace).Create(controller.(*api.ReplicationController))
if err != nil {
return err
}
}
return f.PrintObject(cmd, controller, out)
}

View File

@ -41,91 +41,115 @@ $ kubectl update pods my-pod --patch='{ "apiVersion": "v1beta1", "desiredState":
)
func (f *Factory) NewCmdUpdate(out io.Writer) *cobra.Command {
flags := &struct {
Filenames util.StringList
}{}
var filenames util.StringList
cmd := &cobra.Command{
Use: "update -f filename",
Short: "Update a resource by filename or stdin.",
Long: update_long,
Example: update_example,
Run: func(cmd *cobra.Command, args []string) {
schema, err := f.Validator(cmd)
err := RunUpdate(f, out, cmd, args, filenames)
cmdutil.CheckErr(err)
cmdNamespace, err := f.DefaultNamespace(cmd)
cmdutil.CheckErr(err)
patch := cmdutil.GetFlagString(cmd, "patch")
if len(flags.Filenames) == 0 && len(patch) == 0 {
usageError(cmd, "Must specify --filename or --patch to update")
}
if len(flags.Filenames) != 0 && len(patch) != 0 {
usageError(cmd, "Can not specify both --filename and --patch")
}
// TODO: Make patching work with -f, updating with patched JSON input files
if len(flags.Filenames) == 0 {
name := updateWithPatch(cmd, args, f, patch)
fmt.Fprintf(out, "%s\n", name)
return
}
mapper, typer := f.Object(cmd)
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand(cmd)).
ContinueOnError().
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(flags.Filenames...).
Flatten().
Do()
cmdutil.CheckErr(r.Err())
err = r.Visit(func(info *resource.Info) error {
data, err := info.Mapping.Codec.Encode(info.Object)
if err != nil {
return err
}
if err := schema.ValidateBytes(data); err != nil {
return err
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Update(info.Namespace, info.Name, true, data)
if err != nil {
return err
}
info.Refresh(obj, true)
fmt.Fprintf(out, "%s\n", info.Name)
return nil
})
cmdutil.CheckErr(err)
},
}
cmd.Flags().VarP(&flags.Filenames, "filename", "f", "Filename, directory, or URL to file to use to update the resource.")
cmd.Flags().VarP(&filenames, "filename", "f", "Filename, directory, or URL to file to use to update the resource.")
cmd.Flags().String("patch", "", "A JSON document to override the existing resource. The resource is downloaded, patched with the JSON, then updated.")
return cmd
}
func updateWithPatch(cmd *cobra.Command, args []string, f *Factory, patch string) string {
func RunUpdate(f *Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error {
schema, err := f.Validator(cmd)
if err != nil {
return err
}
cmdNamespace, err := f.DefaultNamespace(cmd)
cmdutil.CheckErr(err)
if err != nil {
return err
}
patch := cmdutil.GetFlagString(cmd, "patch")
if len(filenames) == 0 && len(patch) == 0 {
return cmdutil.UsageError(cmd, "Must specify --filename or --patch to update")
}
if len(filenames) != 0 && len(patch) != 0 {
return cmdutil.UsageError(cmd, "Can not specify both --filename and --patch")
}
// TODO: Make patching work with -f, updating with patched JSON input files
if len(filenames) == 0 {
name, err := updateWithPatch(cmd, args, f, patch)
if err != nil {
return err
}
fmt.Fprintf(out, "%s\n", name)
return nil
}
mapper, typer := f.Object(cmd)
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand(cmd)).
ContinueOnError().
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(filenames...).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
return r.Visit(func(info *resource.Info) error {
data, err := info.Mapping.Codec.Encode(info.Object)
if err != nil {
return err
}
if err := schema.ValidateBytes(data); err != nil {
return err
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Update(info.Namespace, info.Name, true, data)
if err != nil {
return err
}
info.Refresh(obj, true)
fmt.Fprintf(out, "%s\n", info.Name)
return nil
})
}
func updateWithPatch(cmd *cobra.Command, args []string, f *Factory, patch string) (string, error) {
cmdNamespace, err := f.DefaultNamespace(cmd)
if err != nil {
return "", err
}
mapper, _ := f.Object(cmd)
// TODO: use resource.Builder instead
mapping, namespace, name := cmdutil.ResourceFromArgs(cmd, args, mapper, cmdNamespace)
mapping, namespace, name, err := cmdutil.ResourceFromArgs(cmd, args, mapper, cmdNamespace)
if err != nil {
return "", err
}
client, err := f.RESTClient(cmd, mapping)
cmdutil.CheckErr(err)
if err != nil {
return "", err
}
helper := resource.NewHelper(client, mapping)
obj, err := helper.Get(namespace, name)
cmdutil.CheckErr(err)
if err != nil {
return "", err
}
patchedObj, err := cmdutil.Merge(obj, patch, mapping.Kind)
cmdutil.CheckErr(err)
if err != nil {
return "", err
}
data, err := helper.Codec.Encode(patchedObj)
cmdutil.CheckErr(err)
if err != nil {
return "", err
}
_, err = helper.Update(namespace, name, true, data)
cmdutil.CheckErr(err)
return name
return name, err
}

View File

@ -31,10 +31,11 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func CheckErr(err error) {
@ -52,25 +53,26 @@ func CheckErr(err error) {
}
}
func usageError(cmd *cobra.Command, format string, args ...interface{}) {
glog.Errorf(format, args...)
glog.Errorf("See '%s -h' for help.", cmd.CommandPath())
os.Exit(1)
func UsageError(cmd *cobra.Command, format string, args ...interface{}) error {
msg := fmt.Sprintf(format, args...)
return fmt.Errorf("%s\nsee '%s -h' for help.", msg, cmd.CommandPath())
}
func getFlag(cmd *cobra.Command, flag string) *pflag.Flag {
f := cmd.Flags().Lookup(flag)
if f == nil {
glog.Fatalf("flag accessed but not defined for command %s: %s", cmd.Name(), flag)
}
return f
}
func GetFlagString(cmd *cobra.Command, flag string) string {
f := cmd.Flags().Lookup(flag)
if f == nil {
glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
}
f := getFlag(cmd, flag)
return f.Value.String()
}
func GetFlagBool(cmd *cobra.Command, flag string) bool {
f := cmd.Flags().Lookup(flag)
if f == nil {
glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
}
f := getFlag(cmd, flag)
result, err := strconv.ParseBool(f.Value.String())
if err != nil {
glog.Fatalf("Invalid value for a boolean flag: %s", f.Value.String())
@ -80,24 +82,22 @@ func GetFlagBool(cmd *cobra.Command, flag string) bool {
// Assumes the flag has a default value.
func GetFlagInt(cmd *cobra.Command, flag string) int {
f := cmd.Flags().Lookup(flag)
if f == nil {
glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
}
f := getFlag(cmd, flag)
v, err := strconv.Atoi(f.Value.String())
// This is likely not a sufficiently friendly error message, but cobra
// should prevent non-integer values from reaching here.
CheckErr(err)
if err != nil {
glog.Fatalf("unable to convert flag value to int: %v", err)
}
return v
}
func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
f := cmd.Flags().Lookup(flag)
if f == nil {
glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag)
}
f := getFlag(cmd, flag)
v, err := time.ParseDuration(f.Value.String())
CheckErr(err)
if err != nil {
glog.Fatalf("unable to convert flag value to Duration: %v", err)
}
return v
}

View File

@ -27,26 +27,29 @@ import (
)
// 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
// to uniquely locate a resource. Displays a UsageError if that contract is not satisfied, or
// a generic error if any other problems occur.
// DEPRECATED: Use resource.Builder
func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper, cmdNamespace string) (mapping *meta.RESTMapping, namespace, name string) {
func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper, cmdNamespace string) (mapping *meta.RESTMapping, namespace, name string, err error) {
if len(args) != 2 {
usageError(cmd, "Must provide resource and name command line params")
err = UsageError(cmd, "Must provide resource and name command line params")
return
}
resource := args[0]
namespace = cmdNamespace
name = args[1]
if len(name) == 0 || len(resource) == 0 {
usageError(cmd, "Must provide resource and name command line params")
err = UsageError(cmd, "Must provide resource and name command line params")
return
}
version, kind, err := mapper.VersionAndKindForResource(resource)
CheckErr(err)
if err != nil {
return
}
mapping, err = mapper.RESTMapping(kind, version)
CheckErr(err)
return
}
@ -54,41 +57,52 @@ func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper,
// 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.
// DEPRECATED: Use resource.Builder
func ResourceFromFile(filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema, cmdVersion string) (mapping *meta.RESTMapping, namespace, name string, data []byte) {
configData, err := ReadConfigData(filename)
CheckErr(err)
data = configData
func ResourceFromFile(filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema, cmdVersion string) (mapping *meta.RESTMapping, namespace, name string, data []byte, err error) {
data, err = ReadConfigData(filename)
if err != nil {
return
}
objVersion, kind, err := typer.DataVersionAndKind(data)
CheckErr(err)
if err != nil {
return
}
// TODO: allow unversioned objects?
if len(objVersion) == 0 {
CheckErr(fmt.Errorf("the resource in the provided file has no apiVersion defined"))
err = fmt.Errorf("the resource in the provided file has no apiVersion defined")
}
err = schema.ValidateBytes(data)
CheckErr(err)
if err != nil {
return
}
// decode using the version stored with the object (allows codec to vary across versions)
mapping, err = mapper.RESTMapping(kind, objVersion)
CheckErr(err)
if err != nil {
return
}
obj, err := mapping.Codec.Decode(data)
CheckErr(err)
if err != nil {
return
}
meta := mapping.MetadataAccessor
namespace, err = meta.Namespace(obj)
CheckErr(err)
if err != nil {
return
}
name, err = meta.Name(obj)
CheckErr(err)
if err != nil {
return
}
// if the preferred API version differs, get a different mapper
if cmdVersion != objVersion {
mapping, err = mapper.RESTMapping(kind, cmdVersion)
CheckErr(err)
}
return
}

View File

@ -30,17 +30,25 @@ func (f *Factory) NewCmdVersion(out io.Writer) *cobra.Command {
Use: "version",
Short: "Print the client and server version information.",
Run: func(cmd *cobra.Command, args []string) {
if util.GetFlagBool(cmd, "client") {
kubectl.GetClientVersion(out)
return
}
client, err := f.Client(cmd)
err := RunVersion(f, out, cmd)
util.CheckErr(err)
kubectl.GetVersion(out, client)
},
}
cmd.Flags().BoolP("client", "c", false, "Client version only (no server required).")
return cmd
}
func RunVersion(f *Factory, out io.Writer, cmd *cobra.Command) error {
if util.GetFlagBool(cmd, "client") {
kubectl.GetClientVersion(out)
return nil
}
client, err := f.Client(cmd)
if err != nil {
return err
}
kubectl.GetVersion(out, client)
return nil
}