diff --git a/.generated_docs b/.generated_docs
index 1cdfc537781..de64706d093 100644
--- a/.generated_docs
+++ b/.generated_docs
@@ -27,6 +27,7 @@ docs/man/man1/kubectl-config.1
docs/man/man1/kubectl-convert.1
docs/man/man1/kubectl-cordon.1
docs/man/man1/kubectl-create-configmap.1
+docs/man/man1/kubectl-create-deployment.1
docs/man/man1/kubectl-create-namespace.1
docs/man/man1/kubectl-create-quota.1
docs/man/man1/kubectl-create-secret-docker-registry.1
@@ -93,6 +94,7 @@ docs/user-guide/kubectl/kubectl_convert.md
docs/user-guide/kubectl/kubectl_cordon.md
docs/user-guide/kubectl/kubectl_create.md
docs/user-guide/kubectl/kubectl_create_configmap.md
+docs/user-guide/kubectl/kubectl_create_deployment.md
docs/user-guide/kubectl/kubectl_create_namespace.md
docs/user-guide/kubectl/kubectl_create_quota.md
docs/user-guide/kubectl/kubectl_create_secret.md
diff --git a/docs/man/man1/kubectl-create-deployment.1 b/docs/man/man1/kubectl-create-deployment.1
new file mode 100644
index 00000000000..b6fd7a0f989
--- /dev/null
+++ b/docs/man/man1/kubectl-create-deployment.1
@@ -0,0 +1,3 @@
+This file is autogenerated, but we've stopped checking such files into the
+repository to reduce the need for rebases. Please run hack/generate-docs.sh to
+populate this file.
diff --git a/docs/user-guide/kubectl/kubectl_create_deployment.md b/docs/user-guide/kubectl/kubectl_create_deployment.md
new file mode 100644
index 00000000000..d2306069c4f
--- /dev/null
+++ b/docs/user-guide/kubectl/kubectl_create_deployment.md
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
PLEASE NOTE: This document applies to the HEAD of the source tree
+
+If you are using a released version of Kubernetes, you should
+refer to the docs that go with that version.
+
+Documentation for other releases can be found at
+[releases.k8s.io](http://releases.k8s.io).
+
+--
+
+
+
+
+
+This file is autogenerated, but we've stopped checking such files into the
+repository to reduce the need for rebases. Please run hack/generate-docs.sh to
+populate this file.
+
+
+[]()
+
diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go
index f23614a852d..0fab1339329 100644
--- a/pkg/kubectl/cmd/create.go
+++ b/pkg/kubectl/cmd/create.go
@@ -85,6 +85,7 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdCreateConfigMap(f, out))
cmd.AddCommand(NewCmdCreateServiceAccount(f, out))
cmd.AddCommand(NewCmdCreateService(f, out))
+ cmd.AddCommand(NewCmdCreateDeployment(f, out))
return cmd
}
diff --git a/pkg/kubectl/cmd/create_deployment.go b/pkg/kubectl/cmd/create_deployment.go
new file mode 100644
index 00000000000..007ebbe5cfd
--- /dev/null
+++ b/pkg/kubectl/cmd/create_deployment.go
@@ -0,0 +1,80 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+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/renstrom/dedent"
+ "github.com/spf13/cobra"
+
+ "k8s.io/kubernetes/pkg/kubectl"
+ cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
+)
+
+var (
+ deploymentLong = dedent.Dedent(`
+ Create a deployment with the specified name.`)
+
+ deploymentExample = dedent.Dedent(`
+ # Create a new deployment named my-dep that runs the busybox image.
+ kubectl create deployment my-dep --image=busybox`)
+)
+
+// NewCmdCreateDeployment is a macro command to create a new deployment
+func NewCmdCreateDeployment(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "deployment NAME --image=image [--dry-run]",
+ Aliases: []string{"dep"},
+ Short: "Create a deployment with the specified name.",
+ Long: deploymentLong,
+ Example: deploymentExample,
+ Run: func(cmd *cobra.Command, args []string) {
+ err := CreateDeployment(f, cmdOut, cmd, args)
+ cmdutil.CheckErr(err)
+ },
+ }
+ cmdutil.AddApplyAnnotationFlags(cmd)
+ cmdutil.AddValidateFlags(cmd)
+ cmdutil.AddPrinterFlags(cmd)
+ cmdutil.AddGeneratorFlags(cmd, cmdutil.DeploymentBasicV1Beta1GeneratorName)
+ cmd.Flags().StringSlice("image", []string{}, "Image name to run.")
+ cmd.MarkFlagRequired("image")
+ return cmd
+}
+
+// CreateDeployment implements the behavior to run the create deployment command
+func CreateDeployment(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
+ name, err := NameFromCommandArgs(cmd, args)
+ if err != nil {
+ return err
+ }
+ var generator kubectl.StructuredGenerator
+ switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
+ case cmdutil.DeploymentBasicV1Beta1GeneratorName:
+ generator = &kubectl.DeploymentBasicGeneratorV1{Name: name, Images: cmdutil.GetFlagStringSlice(cmd, "image")}
+ default:
+ return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
+ }
+ return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
+ Name: name,
+ StructuredGenerator: generator,
+ DryRun: cmdutil.GetDryRunFlag(cmd),
+ OutputFormat: cmdutil.GetFlagString(cmd, "output"),
+ })
+}
diff --git a/pkg/kubectl/cmd/create_deployment_test.go b/pkg/kubectl/cmd/create_deployment_test.go
new file mode 100644
index 00000000000..36d4b1a58cc
--- /dev/null
+++ b/pkg/kubectl/cmd/create_deployment_test.go
@@ -0,0 +1,54 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+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 (
+ "bytes"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestCreateDeployment(t *testing.T) {
+ depName := "jonny-dep"
+ f, tf, _, _ := NewAPIFactory()
+ tf.Printer = &testPrinter{}
+ tf.Namespace = "test"
+ buf := bytes.NewBuffer([]byte{})
+ cmd := NewCmdCreateDeployment(f, buf)
+ cmd.Flags().Set("dry-run", "true")
+ cmd.Flags().Set("output", "name")
+ cmd.Flags().Set("image", "hollywood/jonny.depp:v2")
+ cmd.Run(cmd, []string{depName})
+ expectedOutput := "deployment/" + depName + "\n"
+ if buf.String() != expectedOutput {
+ t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
+ }
+}
+
+func TestCreateDeploymentNoImage(t *testing.T) {
+ depName := "jonny-dep"
+ f, tf, _, _ := NewAPIFactory()
+ tf.Printer = &testPrinter{}
+ tf.Namespace = "test"
+ buf := bytes.NewBuffer([]byte{})
+ cmd := NewCmdCreateDeployment(f, buf)
+ cmd.Flags().Set("dry-run", "true")
+ cmd.Flags().Set("output", "name")
+ err := CreateDeployment(f, buf, cmd, []string{depName})
+ assert.Error(t, err, "at least one image must be specified")
+}
diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go
index eadfe07533b..08000d846ca 100644
--- a/pkg/kubectl/cmd/util/factory.go
+++ b/pkg/kubectl/cmd/util/factory.go
@@ -165,6 +165,7 @@ const (
HorizontalPodAutoscalerV1Beta1GeneratorName = "horizontalpodautoscaler/v1beta1"
HorizontalPodAutoscalerV1GeneratorName = "horizontalpodautoscaler/v1"
DeploymentV1Beta1GeneratorName = "deployment/v1beta1"
+ DeploymentBasicV1Beta1GeneratorName = "deployment-basic/v1beta1"
JobV1Beta1GeneratorName = "job/v1beta1"
JobV1GeneratorName = "job/v1"
ScheduledJobV2Alpha1GeneratorName = "scheduledjob/v2alpha1"
@@ -192,6 +193,9 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator {
generators["service-loadbalancer"] = map[string]kubectl.Generator{
ServiceLoadBalancerGeneratorV1Name: kubectl.ServiceLoadBalancerGeneratorV1{},
}
+ generators["deployment"] = map[string]kubectl.Generator{
+ DeploymentBasicV1Beta1GeneratorName: kubectl.DeploymentBasicGeneratorV1{},
+ }
generators["run"] = map[string]kubectl.Generator{
RunV1GeneratorName: kubectl.BasicReplicationController{},
RunPodV1GeneratorName: kubectl.BasicPod{},
diff --git a/pkg/kubectl/deployment.go b/pkg/kubectl/deployment.go
new file mode 100644
index 00000000000..035366585cf
--- /dev/null
+++ b/pkg/kubectl/deployment.go
@@ -0,0 +1,107 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+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"
+ "strings"
+
+ "k8s.io/kubernetes/pkg/api"
+ "k8s.io/kubernetes/pkg/api/unversioned"
+ "k8s.io/kubernetes/pkg/apis/extensions"
+ "k8s.io/kubernetes/pkg/runtime"
+)
+
+// DeploymentGeneratorV1 supports stable generation of a deployment
+type DeploymentBasicGeneratorV1 struct {
+ Name string
+ Images []string
+}
+
+// Ensure it supports the generator pattern that uses parameters specified during construction
+var _ StructuredGenerator = &DeploymentBasicGeneratorV1{}
+
+func (DeploymentBasicGeneratorV1) ParamNames() []GeneratorParam {
+ return []GeneratorParam{
+ {"name", true},
+ {"image", true},
+ }
+}
+
+func (s DeploymentBasicGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
+ err := ValidateParams(s.ParamNames(), params)
+ if err != nil {
+ return nil, err
+ }
+ name, isString := params["name"].(string)
+ if !isString {
+ return nil, fmt.Errorf("expected string, saw %v for 'name'", name)
+ }
+ imageStrings, isArray := params["image"].([]string)
+ if !isArray {
+ return nil, fmt.Errorf("expected []string, found :%v", imageStrings)
+ }
+ delegate := &DeploymentBasicGeneratorV1{Name: name, Images: imageStrings}
+ return delegate.StructuredGenerate()
+}
+
+// StructuredGenerate outputs a deployment object using the configured fields
+func (s *DeploymentBasicGeneratorV1) StructuredGenerate() (runtime.Object, error) {
+ if err := s.validate(); err != nil {
+ return nil, err
+ }
+
+ podSpec := api.PodSpec{Containers: []api.Container{}}
+ for _, imageString := range s.Images {
+ imageSplit := strings.Split(imageString, "/")
+ name := imageSplit[len(imageSplit)-1]
+ podSpec.Containers = append(podSpec.Containers, api.Container{Name: name, Image: imageString})
+ }
+
+ // setup default label and selector
+ labels := map[string]string{}
+ labels["app"] = s.Name
+ selector := unversioned.LabelSelector{MatchLabels: labels}
+ deployment := extensions.Deployment{
+ ObjectMeta: api.ObjectMeta{
+ Name: s.Name,
+ Labels: labels,
+ },
+ Spec: extensions.DeploymentSpec{
+ Replicas: 1,
+ Selector: &selector,
+ Template: api.PodTemplateSpec{
+ ObjectMeta: api.ObjectMeta{
+ Labels: labels,
+ },
+ Spec: podSpec,
+ },
+ },
+ }
+ return &deployment, nil
+}
+
+// validate validates required fields are set to support structured generation
+func (s *DeploymentBasicGeneratorV1) validate() error {
+ if len(s.Name) == 0 {
+ return fmt.Errorf("name must be specified")
+ }
+ if len(s.Images) == 0 {
+ return fmt.Errorf("at least one image must be specified")
+ }
+ return nil
+}
diff --git a/pkg/kubectl/deployment_test.go b/pkg/kubectl/deployment_test.go
new file mode 100644
index 00000000000..f538028ee17
--- /dev/null
+++ b/pkg/kubectl/deployment_test.go
@@ -0,0 +1,134 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+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 (
+ "reflect"
+ "testing"
+
+ "k8s.io/kubernetes/pkg/api"
+ "k8s.io/kubernetes/pkg/api/unversioned"
+ "k8s.io/kubernetes/pkg/apis/extensions"
+)
+
+func TestDeploymentGenerate(t *testing.T) {
+ tests := []struct {
+ params map[string]interface{}
+ expected *extensions.Deployment
+ expectErr bool
+ }{
+ {
+ params: map[string]interface{}{
+ "name": "foo",
+ "image": []string{"abc/app:v4"},
+ },
+ expected: &extensions.Deployment{
+ ObjectMeta: api.ObjectMeta{
+ Name: "foo",
+ Labels: map[string]string{"app": "foo"},
+ },
+ Spec: extensions.DeploymentSpec{
+ Replicas: 1,
+ Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"app": "foo"}},
+ Template: api.PodTemplateSpec{
+ ObjectMeta: api.ObjectMeta{
+ Labels: map[string]string{"app": "foo"},
+ },
+ Spec: api.PodSpec{
+ Containers: []api.Container{{Name: "app:v4", Image: "abc/app:v4"}},
+ },
+ },
+ },
+ },
+ expectErr: false,
+ },
+ {
+ params: map[string]interface{}{
+ "name": "foo",
+ "image": []string{"abc/app:v4", "zyx/ape"},
+ },
+ expected: &extensions.Deployment{
+ ObjectMeta: api.ObjectMeta{
+ Name: "foo",
+ Labels: map[string]string{"app": "foo"},
+ },
+ Spec: extensions.DeploymentSpec{
+ Replicas: 1,
+ Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"app": "foo"}},
+ Template: api.PodTemplateSpec{
+ ObjectMeta: api.ObjectMeta{
+ Labels: map[string]string{"app": "foo"},
+ },
+ Spec: api.PodSpec{
+ Containers: []api.Container{{Name: "app:v4", Image: "abc/app:v4"},
+ {Name: "ape", Image: "zyx/ape"}},
+ },
+ },
+ },
+ },
+ expectErr: false,
+ },
+ {
+ params: map[string]interface{}{},
+ expectErr: true,
+ },
+ {
+ params: map[string]interface{}{
+ "name": 1,
+ },
+ expectErr: true,
+ },
+ {
+ params: map[string]interface{}{
+ "name": nil,
+ },
+ expectErr: true,
+ },
+ {
+ params: map[string]interface{}{
+ "name": "foo",
+ "image": []string{},
+ },
+ expectErr: true,
+ },
+ {
+ params: map[string]interface{}{
+ "NAME": "some_value",
+ },
+ expectErr: true,
+ },
+ }
+ generator := DeploymentBasicGeneratorV1{}
+ for index, test := range tests {
+ obj, err := generator.Generate(test.params)
+ switch {
+ case test.expectErr && err != nil:
+ continue // loop, since there's no output to check
+ case test.expectErr && err == nil:
+ t.Errorf("%v: expected error and didn't get one", index)
+ continue // loop, no expected output object
+ case !test.expectErr && err != nil:
+ t.Errorf("%v: unexpected error %v", index, err)
+ continue // loop, no output object
+ case !test.expectErr && err == nil:
+ // do nothing and drop through
+ }
+ if !reflect.DeepEqual(obj.(*extensions.Deployment), test.expected) {
+ t.Errorf("%v\nexpected:\n%#v\nsaw:\n%#v", index, test.expected, obj.(*extensions.Deployment))
+ }
+ }
+}