Commit 1bb39e2b authored by Piotr Szczesniak's avatar Piotr Szczesniak

Merge pull request #12519 from feihujiang/kubectlExposeAcceptFileNameParam

Kubectl expose command accepts a filename param
parents 0ded91c5 c0b20988
...@@ -700,6 +700,12 @@ _kubectl_expose() ...@@ -700,6 +700,12 @@ _kubectl_expose()
flags+=("--container-port=") flags+=("--container-port=")
flags+=("--create-external-load-balancer") flags+=("--create-external-load-balancer")
flags+=("--dry-run") flags+=("--dry-run")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--generator=") flags+=("--generator=")
flags+=("--help") flags+=("--help")
flags+=("-h") flags+=("-h")
......
...@@ -35,6 +35,10 @@ re\-use the labels from the resource it exposes. ...@@ -35,6 +35,10 @@ re\-use the labels from the resource it exposes.
If true, only print the object that would be sent, without creating it. If true, only print the object that would be sent, without creating it.
.PP .PP
\fB\-f\fP, \fB\-\-filename\fP=[]
Filename, directory, or URL to a file identifying the resource to expose a service
.PP
\fB\-\-generator\fP="service/v2" \fB\-\-generator\fP="service/v2"
The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'. The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.
...@@ -206,6 +210,9 @@ re\-use the labels from the resource it exposes. ...@@ -206,6 +210,9 @@ re\-use the labels from the resource it exposes.
// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000. // Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
$ kubectl expose rc nginx \-\-port=80 \-\-target\-port=8000 $ kubectl expose rc nginx \-\-port=80 \-\-target\-port=8000
# Creates a service for a replication controller identified by type and name specified in "nginx\-controller.yaml", which serves on port 80 and connects to the containers on port 8000.
$ kubectl expose \-f nginx\-controller.yaml \-\-port=80 \-\-target\-port=8000
// Creates a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx\-https" // Creates a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx\-https"
$ kubectl expose service nginx \-\-port=443 \-\-target\-port=8443 \-\-name=nginx\-https $ kubectl expose service nginx \-\-port=443 \-\-target\-port=8443 \-\-name=nginx\-https
......
...@@ -45,7 +45,7 @@ selector for a new Service on the specified port. If no labels are specified, th ...@@ -45,7 +45,7 @@ selector for a new Service on the specified port. If no labels are specified, th
re-use the labels from the resource it exposes. re-use the labels from the resource it exposes.
``` ```
kubectl expose TYPE NAME --port=port [--protocol=TCP|UDP] [--target-port=number-or-name] [--name=name] [--public-ip=ip] [--type=type] kubectl expose (-f FILENAME | TYPE NAME) --port=port [--protocol=TCP|UDP] [--target-port=number-or-name] [--name=name] [--public-ip=ip] [--type=type]
``` ```
### Examples ### Examples
...@@ -54,6 +54,9 @@ kubectl expose TYPE NAME --port=port [--protocol=TCP|UDP] [--target-port=number- ...@@ -54,6 +54,9 @@ kubectl expose TYPE NAME --port=port [--protocol=TCP|UDP] [--target-port=number-
// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000. // Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
$ kubectl expose rc nginx --port=80 --target-port=8000 $ kubectl expose rc nginx --port=80 --target-port=8000
# Creates a service for a replication controller identified by type and name specified in "nginx-controller.yaml", which serves on port 80 and connects to the containers on port 8000.
$ kubectl expose -f nginx-controller.yaml --port=80 --target-port=8000
// Creates a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https" // Creates a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https"
$ kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https $ kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https
...@@ -67,6 +70,7 @@ $ kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream ...@@ -67,6 +70,7 @@ $ kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream
--container-port="": Synonym for --target-port --container-port="": Synonym for --target-port
--create-external-load-balancer[=false]: If true, create an external load balancer for this service (trumped by --type). Implementation is cloud provider dependent. Default is 'false'. --create-external-load-balancer[=false]: If true, create an external load balancer for this service (trumped by --type). Implementation is cloud provider dependent. Default is 'false'.
--dry-run[=false]: If true, only print the object that would be sent, without creating it. --dry-run[=false]: If true, only print the object that would be sent, without creating it.
-f, --filename=[]: Filename, directory, or URL to a file identifying the resource to expose a service
--generator="service/v2": The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'. --generator="service/v2": The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.
-h, --help[=false]: help for expose -h, --help[=false]: help for expose
-l, --labels="": Labels to apply to the service created by this call. -l, --labels="": Labels to apply to the service created by this call.
...@@ -118,7 +122,7 @@ $ kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream ...@@ -118,7 +122,7 @@ $ kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-08-07 19:25:02.004335817 +0000 UTC ###### Auto generated by spf13/cobra at 2015-08-12 08:16:20.804863501 +0000 UTC
<!-- BEGIN MUNGE: GENERATED_ANALYTICS --> <!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_expose.md?pixel)]() [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_expose.md?pixel)]()
......
...@@ -20,11 +20,11 @@ import ( ...@@ -20,11 +20,11 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"github.com/spf13/cobra"
) )
const ( const (
...@@ -37,6 +37,9 @@ re-use the labels from the resource it exposes.` ...@@ -37,6 +37,9 @@ re-use the labels from the resource it exposes.`
expose_example = `// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000. expose_example = `// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
$ kubectl expose rc nginx --port=80 --target-port=8000 $ kubectl expose rc nginx --port=80 --target-port=8000
# Creates a service for a replication controller identified by type and name specified in "nginx-controller.yaml", which serves on port 80 and connects to the containers on port 8000.
$ kubectl expose -f nginx-controller.yaml --port=80 --target-port=8000
// Creates a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https" // Creates a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https"
$ kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https $ kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https
...@@ -46,7 +49,7 @@ $ kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream` ...@@ -46,7 +49,7 @@ $ kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream`
func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "expose TYPE NAME --port=port [--protocol=TCP|UDP] [--target-port=number-or-name] [--name=name] [--public-ip=ip] [--type=type]", Use: "expose (-f FILENAME | TYPE NAME) --port=port [--protocol=TCP|UDP] [--target-port=number-or-name] [--name=name] [--public-ip=ip] [--type=type]",
Short: "Take a replicated application and expose it as Kubernetes Service", Short: "Take a replicated application and expose it as Kubernetes Service",
Long: expose_long, Long: expose_long,
Example: expose_example, Example: expose_example,
...@@ -71,11 +74,14 @@ func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command { ...@@ -71,11 +74,14 @@ func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().String("overrides", "", "An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.") cmd.Flags().String("overrides", "", "An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.")
cmd.Flags().String("name", "", "The name for the newly created object.") cmd.Flags().String("name", "", "The name for the newly created object.")
cmd.Flags().String("session-affinity", "", "If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'") cmd.Flags().String("session-affinity", "", "If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'")
usage := "Filename, directory, or URL to a file identifying the resource to expose a service"
kubectl.AddJsonFilenameFlag(cmd, usage)
return cmd return cmd
} }
func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
namespace, _, err := f.DefaultNamespace() namespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
} }
...@@ -84,6 +90,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str ...@@ -84,6 +90,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
ContinueOnError(). ContinueOnError().
NamespaceParam(namespace).DefaultNamespace(). NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, cmdutil.GetFlagStringSlice(cmd, "filename")...).
ResourceTypeOrNameArgs(false, args...). ResourceTypeOrNameArgs(false, args...).
Flatten(). Flatten().
Do() Do()
...@@ -91,10 +98,6 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str ...@@ -91,10 +98,6 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
if err != nil { if err != nil {
return err return err
} }
mapping, err := r.ResourceMapping()
if err != nil {
return err
}
infos, err := r.Infos() infos, err := r.Infos()
if err != nil { if err != nil {
return err return err
...@@ -103,6 +106,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str ...@@ -103,6 +106,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return fmt.Errorf("multiple resources provided: %v", args) return fmt.Errorf("multiple resources provided: %v", args)
} }
info := infos[0] info := infos[0]
mapping := info.ResourceMapping()
// Get the input object // Get the input object
client, err := f.RESTClient(mapping) client, err := f.RESTClient(mapping)
...@@ -201,6 +205,5 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str ...@@ -201,6 +205,5 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return err return err
} }
} }
return f.PrintObject(cmd, object, out) return f.PrintObject(cmd, object, out)
} }
...@@ -202,7 +202,6 @@ func TestRunExposeService(t *testing.T) { ...@@ -202,7 +202,6 @@ func TestRunExposeService(t *testing.T) {
cmd.Flags().Set(flag, value) cmd.Flags().Set(flag, value)
} }
cmd.Run(cmd, test.args) cmd.Run(cmd, test.args)
if len(test.expected) > 0 { if len(test.expected) > 0 {
out := buf.String() out := buf.String()
if !strings.Contains(out, test.expected) { if !strings.Contains(out, test.expected) {
...@@ -211,3 +210,76 @@ func TestRunExposeService(t *testing.T) { ...@@ -211,3 +210,76 @@ func TestRunExposeService(t *testing.T) {
} }
} }
} }
func TestRunExposeServiceFromFile(t *testing.T) {
test := struct {
calls map[string]string
input runtime.Object
flags map[string]string
output runtime.Object
expected string
status int
}{
calls: map[string]string{
"GET": "/namespaces/test/services/redis-master",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
TypeMeta: api.TypeMeta{Kind: "Service", APIVersion: "v1"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "12", Labels: map[string]string{"svc": "test"}},
TypeMeta: api.TypeMeta{Kind: "Service", APIVersion: "v1"},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Name: "default",
Protocol: api.Protocol("UDP"),
Port: 14,
},
},
Selector: map[string]string{"func": "stream"},
},
},
status: 200,
}
f, tf, codec := NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &client.FakeRESTClient{
Codec: codec,
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.calls[m] && m == "GET":
return &http.Response{StatusCode: test.status, Body: objBody(codec, test.input)}, nil
case p == test.calls[m] && m == "POST":
return &http.Response{StatusCode: test.status, Body: objBody(codec, test.output)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdExposeService(f, buf)
cmd.SetOutput(buf)
for flag, value := range test.flags {
cmd.Flags().Set(flag, value)
}
cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-service.yaml")
cmd.Run(cmd, []string{})
if len(test.expected) > 0 {
out := buf.String()
if !strings.Contains(out, test.expected) {
t.Errorf("unexpected output: %s", out)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment