diff --git a/docs/cli.md b/docs/cli.md index 39a9546a8fb..faec73a8d2a 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -125,6 +125,64 @@ Usage: --validate=false: If true, use a schema to validate the input before sending it ``` +### run-container +Easily run one or more replicas of an image. + +Creates a replica controller running the specified image, with one or more replicas. + +Examples: +```sh + $ kubectl run-container nginx --image=dockerfile/nginx + + + $ kubectl run-container nginx --image=dockerfile/nginx --replicas=5 + + + $ kubectl run-container nginx --image=dockerfile/nginx --dry-run + + +Usage: + kubectl run-container --image= [--replicas=replicas] [--dry-run=] [flags] + + Available Flags: + --alsologtostderr=false: log to standard error as well as files + --api-version="": The API version to use when talking to the server + -a, --auth-path="": Path to the auth info file. If missing, prompt the user. Only used if using https. + --certificate-authority="": Path to a cert. file for the certificate authority. + --client-certificate="": Path to a client key file for TLS. + --client-key="": Path to a client key file for TLS. + --cluster="": The name of the kubeconfig cluster to use + --context="": The name of the kubeconfig context to use + --dry-run=false: If true, only print the object that would be sent, don't actually do anything + --generator="run-container-controller-v1": The name of the api generator that you want to use. Default 'run-container-controller-v1' + -h, --help=false: help for run-container + --image="": The image for the container you wish to run. + --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + --kubeconfig="": Path to the kubeconfig file to use for CLI requests. + -l, --labels="": Labels to apply to the pod(s) created by this call to run. + --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace + --log_dir=: If non-empty, write log files in this directory + --log_flush_frequency=5s: Maximum number of seconds between log flushes + --logtostderr=true: log to standard error instead of files + --match-server-version=false: Require server version to match client version + -n, --namespace="": If present, the namespace scope for this CLI request. + --no-headers=false: When using the default output, don't print headers + --ns-path="/Users/bburns/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + -o, --output="": Output format: json|yaml|template|templatefile + --output-version="": Output the formatted object with the given version (default api-version) + -r, --replicas=1: Number of replicas to create for this container. Default 1 + -s, --server="": The address of the Kubernetes API server + --stderrthreshold=2: logs at or above this threshold go to stderr + -t, --template="": Template string or path to template file to use when -o=template or -o=templatefile. + --token="": Bearer token for authentication to the API server. + --user="": The name of the kubeconfig user to use + --v=0: log level for V logs + --validate=false: If true, use a schema to validate the input before sending it + --vmodule=: comma-separated list of pattern=N settings for file-filtered logging $ kubectl run nginx dockerfile/nginx + +``` + + #### create Create a resource by filename or stdin. diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 533b5a510db..c8255980361 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -174,6 +174,8 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, cmds.AddCommand(f.NewCmdLog(out)) cmds.AddCommand(f.NewCmdRollingUpdate(out)) + cmds.AddCommand(f.NewCmdRunContainer(out)) + return cmds } diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index de43cd32f95..01989534e82 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -22,7 +22,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" @@ -60,10 +59,7 @@ Examples: RunGet(f, out, cmd, args) }, } - cmd.Flags().StringP("output", "o", "", "Output format: json|yaml|template|templatefile") - cmd.Flags().String("output-version", "", "Output the formatted object with the given version (default api-version)") - cmd.Flags().Bool("no-headers", false, "When using the default output, don't print headers") - cmd.Flags().StringP("template", "t", "", "Template string or path to template file to use when -o=template or -o=templatefile.") + AddPrinterFlags(cmd) cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on") cmd.Flags().BoolP("watch", "w", false, "After listing/getting the requested object, watch for changes.") cmd.Flags().Bool("watch-only", false, "Watch for changes to the requseted object(s), without listing/getting first.") @@ -90,7 +86,7 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { mapping, err := r.ResourceMapping() checkErr(err) - printer, err := printerForMapping(f, cmd, mapping) + printer, err := PrinterForMapping(f, cmd, mapping) checkErr(err) obj, err := r.Object() @@ -116,14 +112,13 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { return } - printer, generic, err := printerForCommand(cmd) - checkErr(err) - b := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)). NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace(). SelectorParam(selector). ResourceTypeOrNameArgs(args...). Latest() + printer, generic, err := PrinterForCommand(cmd) + checkErr(err) if generic { // the outermost object will be converted to the output-version @@ -147,7 +142,7 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { // use the default printer for each object err = b.Do().Visit(func(r *resource.Info) error { - printer, err := printerForMapping(f, cmd, r.Mapping) + printer, err := PrinterForMapping(f, cmd, r.Mapping) if err != nil { return err } @@ -155,47 +150,3 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { }) checkErr(err) } - -// outputVersion returns the preferred output version for generic content (JSON, YAML, or templates) -func outputVersion(cmd *cobra.Command) string { - outputVersion := GetFlagString(cmd, "output-version") - if len(outputVersion) == 0 { - outputVersion = GetFlagString(cmd, "api-version") - } - return outputVersion -} - -// printerForCommand returns the default printer for this command. -func printerForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error) { - outputFormat := GetFlagString(cmd, "output") - templateFile := GetFlagString(cmd, "template") - if len(outputFormat) == 0 && len(templateFile) != 0 { - outputFormat = "template" - } - - return kubectl.GetPrinter(outputFormat, templateFile) -} - -// printerForMapping returns a printer suitable for displaying the provided resource type. -func printerForMapping(f *Factory, cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.ResourcePrinter, error) { - printer, ok, err := printerForCommand(cmd) - if err != nil { - return nil, err - } - if ok { - version := outputVersion(cmd) - if len(version) == 0 { - version = mapping.APIVersion - } - if len(version) == 0 { - return nil, fmt.Errorf("you must specify an output-version when using this output format") - } - printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, version) - } else { - printer, err = f.Printer(cmd, mapping, GetFlagBool(cmd, "no-headers")) - if err != nil { - return nil, err - } - } - return printer, nil -} diff --git a/pkg/kubectl/cmd/printing.go b/pkg/kubectl/cmd/printing.go new file mode 100644 index 00000000000..c2d4ab2486e --- /dev/null +++ b/pkg/kubectl/cmd/printing.go @@ -0,0 +1,107 @@ +/* +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 cmd + +import ( + "fmt" + "io" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + + "github.com/spf13/cobra" +) + +func AddPrinterFlags(cmd *cobra.Command) { + cmd.Flags().StringP("output", "o", "", "Output format: json|yaml|template|templatefile") + cmd.Flags().String("output-version", "", "Output the formatted object with the given version (default api-version)") + cmd.Flags().Bool("no-headers", false, "When using the default output, don't print headers") + cmd.Flags().StringP("template", "t", "", "Template string or path to template file to use when -o=template or -o=templatefile.") +} + +// PrintObject prints an api object given command line flags to modify the output format +func PrintObject(cmd *cobra.Command, obj runtime.Object, f *Factory, out io.Writer) { + mapper, _ := f.Object(cmd) + _, kind, err := api.Scheme.ObjectVersionAndKind(obj) + checkErr(err) + + mapping, err := mapper.RESTMapping(kind) + checkErr(err) + + printer, ok, err := PrinterForCommand(cmd) + checkErr(err) + + if ok { + version := outputVersion(cmd) + if len(version) == 0 { + version = mapping.APIVersion + } + if len(version) == 0 { + checkErr(fmt.Errorf("you must specify an output-version when using this output format")) + } + printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, version) + } + printer.PrintObj(obj, out) +} + +// outputVersion returns the preferred output version for generic content (JSON, YAML, or templates) +func outputVersion(cmd *cobra.Command) string { + outputVersion := GetFlagString(cmd, "output-version") + if len(outputVersion) == 0 { + outputVersion = GetFlagString(cmd, "api-version") + } + return outputVersion +} + +// PrinterForCommand returns the default printer for this command. +// Requires that printer flags have been added to cmd (see AddPrinterFlags). +func PrinterForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error) { + outputFormat := GetFlagString(cmd, "output") + templateFile := GetFlagString(cmd, "template") + if len(outputFormat) == 0 && len(templateFile) != 0 { + outputFormat = "template" + } + + return kubectl.GetPrinter(outputFormat, templateFile) +} + +// PrinterForMapping returns a printer suitable for displaying the provided resource type. +// Requires that printer flags have been added to cmd (see AddPrinterFlags). +func PrinterForMapping(f *Factory, cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.ResourcePrinter, error) { + printer, ok, err := PrinterForCommand(cmd) + if err != nil { + return nil, err + } + if ok { + version := outputVersion(cmd) + if len(version) == 0 { + version = mapping.APIVersion + } + if len(version) == 0 { + return nil, fmt.Errorf("you must specify an output-version when using this output format") + } + printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, version) + } else { + printer, err = f.Printer(cmd, mapping, GetFlagBool(cmd, "no-headers")) + if err != nil { + return nil, err + } + } + return printer, nil +} diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go new file mode 100644 index 00000000000..f2f4ba0af04 --- /dev/null +++ b/pkg/kubectl/cmd/run.go @@ -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 cmd + +import ( + "fmt" + "io" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" + "github.com/spf13/cobra" +) + +func (f *Factory) NewCmdRunContainer(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "run-container --image= [--replicas=replicas] [--dry-run=]", + Short: "Run a particular image on the cluster.", + Long: `Create and run a particular image, possibly replicated. +Creates a replication controller to manage the created container(s) + +Examples: + $ kubectl run-container nginx --image=dockerfile/nginx + + + $ kubectl run-container nginx --image=dockerfile/nginx --replicas=5 + + + $ kubectl run-container nginx --image=dockerfile/nginx --dry-run + `, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + usageError(cmd, " is required for run") + } + + namespace := GetKubeNamespace(cmd) + client, err := f.Client(cmd) + checkErr(err) + + generatorName := GetFlagString(cmd, "generator") + generator, found := kubectl.Generators[generatorName] + if !found { + usageError(cmd, fmt.Sprintf("Generator: %s not found.", generator)) + } + names := generator.ParamNames() + params, err := kubectl.MakeParams(cmd, names) + params["name"] = args[0] + + err = kubectl.ValidateParams(names, params) + checkErr(err) + + controller, err := generator.Generate(params) + checkErr(err) + + // TODO: extract this flag to a central location, when such a location exists. + if !GetFlagBool(cmd, "dry-run") { + controller, err = client.ReplicationControllers(namespace).Create(controller.(*api.ReplicationController)) + checkErr(err) + } + PrintObject(cmd, controller, f, out) + }, + } + AddPrinterFlags(cmd) + cmd.Flags().String("generator", "run-container/v1", "The name of the api generator that you want to use. Default 'run-container-controller-v1'") + cmd.Flags().String("image", "", "The image for the container you wish to run.") + cmd.Flags().IntP("replicas", "r", 1, "Number of replicas to create for this container. Default 1") + cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, don't actually do anything") + cmd.Flags().StringP("labels", "l", "", "Labels to apply to the pod(s) created by this call to run.") + return cmd +} diff --git a/pkg/kubectl/generate.go b/pkg/kubectl/generate.go new file mode 100644 index 00000000000..74cb86ad33d --- /dev/null +++ b/pkg/kubectl/generate.go @@ -0,0 +1,70 @@ +/* +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" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/spf13/cobra" +) + +// GeneratorParam is a parameter for a generator +// TODO: facilitate structured json generator input schemes +type GeneratorParam struct { + Name string + Required bool +} + +// Generator is an interface for things that can generate API objects from input parameters. +type Generator interface { + // Generate creates an API object given a set of parameters + Generate(params map[string]string) (runtime.Object, error) + // ParamNames returns the list of parameters that this generator uses + ParamNames() []GeneratorParam +} + +// Generators is a global list of known generators. +// TODO: Dynamically create this from a list of template files? +var Generators map[string]Generator = map[string]Generator{ + "run-container/v1": BasicReplicationController{}, +} + +// ValidateParams ensures that all required params are present in the params map +func ValidateParams(paramSpec []GeneratorParam, params map[string]string) error { + for ix := range paramSpec { + if paramSpec[ix].Required { + value, found := params[paramSpec[ix].Name] + if !found || len(value) == 0 { + return fmt.Errorf("Parameter: %s is required", paramSpec[ix].Name) + } + } + } + return nil +} + +// MakeParams is a utility that creates generator parameters from a command line +func MakeParams(cmd *cobra.Command, params []GeneratorParam) (map[string]string, error) { + result := map[string]string{} + for ix := range params { + f := cmd.Flags().Lookup(params[ix].Name) + if f != nil { + result[params[ix].Name] = f.Value.String() + } + } + return result, nil +} diff --git a/pkg/kubectl/run.go b/pkg/kubectl/run.go new file mode 100644 index 00000000000..46546cfe904 --- /dev/null +++ b/pkg/kubectl/run.go @@ -0,0 +1,96 @@ +/* +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 ( + "strconv" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/golang/glog" +) + +type BasicReplicationController struct{} + +func (BasicReplicationController) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"labels", false}, + {"name", true}, + {"replicas", true}, + {"image", true}, + } +} + +func (BasicReplicationController) Generate(params map[string]string) (runtime.Object, error) { + // TODO: extract this flag to a central location. + labelString, found := params["labels"] + var labels map[string]string + if found && len(labelString) > 0 { + labels = ParseLabels(labelString) + } else { + labels = map[string]string{ + "run-container": params["name"], + } + } + count, err := strconv.Atoi(params["replicas"]) + if err != nil { + return nil, err + } + controller := api.ReplicationController{ + ObjectMeta: api.ObjectMeta{ + Name: params["name"], + Labels: labels, + }, + Spec: api.ReplicationControllerSpec{ + Replicas: count, + Selector: labels, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: labels, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: params["name"], + Image: params["image"], + }, + }, + }, + }, + }, + } + return &controller, nil +} + +// TODO: extract this to a common location. +func ParseLabels(labelString string) map[string]string { + if len(labelString) == 0 { + return nil + } + labels := map[string]string{} + labelSpecs := strings.Split(labelString, ",") + for ix := range labelSpecs { + labelSpec := strings.Split(labelSpecs[ix], "=") + if len(labelSpec) != 2 { + glog.Errorf("unexpected label spec: %s", labelSpecs[ix]) + continue + } + labels[labelSpec[0]] = labelSpec[1] + } + return labels +}