diff --git a/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/cmd/expose.go b/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/cmd/expose.go index 3cc960fdc589..1d5d36c1156b 100644 --- a/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/cmd/expose.go +++ b/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/cmd/expose.go @@ -19,6 +19,7 @@ package cmd import ( "fmt" "io" + "strings" "github.com/spf13/cobra" @@ -163,7 +164,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str case 1: params["port"] = ports[0] default: - return cmdutil.UsageError(cmd, fmt.Sprintf("multiple ports to choose from: %v, please explicitly specify a port using the --port flag.", ports)) + params["ports"] = strings.Join(ports, ",") } } if kubectl.IsZero(params["labels"]) { diff --git a/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/service.go b/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/service.go index edee6544913f..34e4825c45d3 100644 --- a/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/service.go +++ b/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/service.go @@ -19,6 +19,7 @@ package kubectl import ( "fmt" "strconv" + "strings" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/runtime" @@ -52,7 +53,12 @@ func paramNames() []GeneratorParam { {"default-name", true}, {"name", false}, {"selector", true}, - {"port", true}, + // port will be used if a user specifies --port OR the exposed object + // has one port + {"port", false}, + // ports will be used iff a user doesn't specify --port AND the + // exposed object has multiple ports + {"ports", false}, {"labels", false}, {"external-ip", false}, {"create-external-load-balancer", false}, @@ -100,19 +106,41 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) { return nil, fmt.Errorf("'name' is a required parameter.") } } - portString, found := params["port"] - if !found { - return nil, fmt.Errorf("'port' is a required parameter.") - } - port, err := strconv.Atoi(portString) - if err != nil { - return nil, err - } + ports := []api.ServicePort{} servicePortName, found := params["port-name"] if !found { // Leave the port unnamed. servicePortName = "" } + // ports takes precedence over port since it will be + // specified only when the user hasn't specified a port + // via --port and the exposed object has multiple ports. + var portString string + if portString, found = params["ports"]; !found { + portString, found = params["port"] + if !found { + return nil, fmt.Errorf("'port' is a required parameter.") + } + } + portStringSlice := strings.Split(portString, ",") + for i, stillPortString := range portStringSlice { + port, err := strconv.Atoi(stillPortString) + if err != nil { + return nil, err + } + name := servicePortName + // If we are going to assign multiple ports to a service, we need to + // generate a different name for each one. + if len(portStringSlice) > 1 { + name = fmt.Sprintf("port-%d", i+1) + } + ports = append(ports, api.ServicePort{ + Name: name, + Port: port, + Protocol: api.Protocol(params["protocol"]), + }) + } + service := api.Service{ ObjectMeta: api.ObjectMeta{ Name: name, @@ -120,27 +148,31 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) { }, Spec: api.ServiceSpec{ Selector: selector, - Ports: []api.ServicePort{ - { - Name: servicePortName, - Port: port, - Protocol: api.Protocol(params["protocol"]), - }, - }, + Ports: ports, }, } - targetPort, found := params["target-port"] + targetPortString, found := params["target-port"] if !found { - targetPort, found = params["container-port"] + targetPortString, found = params["container-port"] } - if found && len(targetPort) > 0 { - if portNum, err := strconv.Atoi(targetPort); err != nil { - service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromString(targetPort) + if found && len(targetPortString) > 0 { + var targetPort util.IntOrString + if portNum, err := strconv.Atoi(targetPortString); err != nil { + targetPort = util.NewIntOrStringFromString(targetPortString) } else { - service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromInt(portNum) + targetPort = util.NewIntOrStringFromInt(portNum) + } + // Use the same target-port for every port + for i := range service.Spec.Ports { + service.Spec.Ports[i].TargetPort = targetPort } } else { - service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromInt(port) + // If --target-port or --container-port haven't been specified, this + // should be the same as Port + for i := range service.Spec.Ports { + port := service.Spec.Ports[i].Port + service.Spec.Ports[i].TargetPort = util.NewIntOrStringFromInt(port) + } } if params["create-external-load-balancer"] == "true" { service.Spec.Type = api.ServiceTypeLoadBalancer diff --git a/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/service_test.go b/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/service_test.go index 5345bdf70e8d..b54f4deecddf 100644 --- a/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/service_test.go +++ b/Godeps/_workspace/src/k8s.io/kubernetes/pkg/kubectl/service_test.go @@ -303,6 +303,107 @@ func TestGenerateService(t *testing.T) { }, }, }, + { + generator: ServiceGeneratorV1{}, + params: map[string]interface{}{ + "selector": "foo=bar", + "name": "test", + "ports": "80,443", + "protocol": "TCP", + "container-port": "foobar", + }, + expected: api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + }, + Ports: []api.ServicePort{ + { + Name: "port-1", + Port: 80, + Protocol: api.ProtocolTCP, + TargetPort: util.NewIntOrStringFromString("foobar"), + }, + { + Name: "port-2", + Port: 443, + Protocol: api.ProtocolTCP, + TargetPort: util.NewIntOrStringFromString("foobar"), + }, + }, + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar", + "name": "test", + "ports": "80,443", + "protocol": "UDP", + "target-port": "1234", + }, + expected: api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + }, + Ports: []api.ServicePort{ + { + Name: "port-1", + Port: 80, + Protocol: api.ProtocolUDP, + TargetPort: util.NewIntOrStringFromInt(1234), + }, + { + Name: "port-2", + Port: 443, + Protocol: api.ProtocolUDP, + TargetPort: util.NewIntOrStringFromInt(1234), + }, + }, + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar", + "name": "test", + "ports": "80,443", + "protocol": "TCP", + }, + expected: api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + }, + Ports: []api.ServicePort{ + { + Name: "port-1", + Port: 80, + Protocol: api.ProtocolTCP, + TargetPort: util.NewIntOrStringFromInt(80), + }, + { + Name: "port-2", + Port: 443, + Protocol: api.ProtocolTCP, + TargetPort: util.NewIntOrStringFromInt(443), + }, + }, + }, + }, + }, } for _, test := range tests { obj, err := test.generator.Generate(test.params) diff --git a/hack/util.sh b/hack/util.sh index 1c47abf34359..35159227188b 100644 --- a/hack/util.sh +++ b/hack/util.sh @@ -774,3 +774,22 @@ os::util::sed() { sed -i'' $@ fi } + +os::util::get_object_assert() { + local object=$1 + local request=$2 + local expected=$3 + + res=$(eval oc get $object -o go-template=\"$request\") + + if [[ "$res" =~ ^$expected$ ]]; then + echo "Successful get $object $request: $res" + return 0 + else + echo "FAIL!" + echo "Get $object $request" + echo " Expected: $expected" + echo " Got: $res" + return 1 + fi +} diff --git a/pkg/route/generator/generate.go b/pkg/route/generator/generate.go index ecb74da852c2..5052786fba2a 100644 --- a/pkg/route/generator/generate.go +++ b/pkg/route/generator/generate.go @@ -2,10 +2,13 @@ package generator import ( "fmt" + "strconv" + "strings" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util" "github.com/openshift/origin/pkg/route/api" ) @@ -21,6 +24,8 @@ func (RouteGenerator) ParamNames() []kubectl.GeneratorParam { return []kubectl.GeneratorParam{ {"labels", false}, {"default-name", true}, + {"port", false}, + {"ports", false}, {"name", false}, {"hostname", false}, } @@ -58,6 +63,17 @@ func (RouteGenerator) Generate(genericParams map[string]interface{}) (runtime.Ob } } + var portString string + portString, found = params["port"] + if !found || len(portString) == 0 { + portString = strings.Split(params["ports"], ",")[0] + } + + port, err := strconv.ParseInt(portString, 10, 0) + if err != nil { + return nil, err + } + return &api.Route{ ObjectMeta: kapi.ObjectMeta{ Name: name, @@ -68,6 +84,12 @@ func (RouteGenerator) Generate(genericParams map[string]interface{}) (runtime.Ob To: kapi.ObjectReference{ Name: params["default-name"], }, + Port: &api.RoutePort{ + TargetPort: util.IntOrString{ + Kind: util.IntstrInt, + IntVal: int(port), + }, + }, }, }, nil } diff --git a/pkg/route/generator/generate_test.go b/pkg/route/generator/generate_test.go new file mode 100644 index 000000000000..051ca9210cbf --- /dev/null +++ b/pkg/route/generator/generate_test.go @@ -0,0 +1,88 @@ +package generator + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util" + + routeapi "github.com/openshift/origin/pkg/route/api" +) + +func TestGenerateRoute(t *testing.T) { + generator := RouteGenerator{} + + tests := []struct { + params map[string]interface{} + expected routeapi.Route + }{ + { + params: map[string]interface{}{ + "labels": "foo=bar", + "name": "test", + "default-name": "someservice", + "port": "80", + "hostname": "www.example.com", + }, + expected: routeapi.Route{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: routeapi.RouteSpec{ + Host: "www.example.com", + To: api.ObjectReference{ + Name: "someservice", + }, + Port: &routeapi.RoutePort{ + TargetPort: util.IntOrString{ + Kind: util.IntstrInt, + IntVal: 80, + }, + }, + }, + }, + }, + { + params: map[string]interface{}{ + "labels": "foo=bar", + "name": "test", + "default-name": "someservice", + "ports": "80,443", + "hostname": "www.example.com", + }, + expected: routeapi.Route{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: routeapi.RouteSpec{ + Host: "www.example.com", + To: api.ObjectReference{ + Name: "someservice", + }, + Port: &routeapi.RoutePort{ + TargetPort: util.IntOrString{ + Kind: util.IntstrInt, + IntVal: 80, + }, + }, + }, + }, + }, + } + for _, test := range tests { + obj, err := generator.Generate(test.params) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !reflect.DeepEqual(obj, &test.expected) { + t.Errorf("expected:\n%#v\ngot\n%#v\n", &test.expected, obj) + } + } +} diff --git a/test/cmd/basicresources.sh b/test/cmd/basicresources.sh index da72630d3f8c..a5486d70e59c 100755 --- a/test/cmd/basicresources.sh +++ b/test/cmd/basicresources.sh @@ -75,6 +75,10 @@ oc expose svc/external [ "$(oc get route external | grep 'external=service')" ] oc delete route external oc delete svc external +# Expose multiport service and verify we set a port in the route +oc create -f test/fixtures/multiport-service.yaml +oc expose svc/frontend --name route-with-set-port +os::util::get_object_assert 'route route-with-set-port' "{{.spec.port.targetPort}}" '5432' echo "expose: ok" oc delete all --all diff --git a/test/fixtures/multiport-service.yaml b/test/fixtures/multiport-service.yaml new file mode 100644 index 000000000000..5f2400e5ea50 --- /dev/null +++ b/test/fixtures/multiport-service.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: List +items: +- apiVersion: v1 + kind: Service + metadata: + creationTimestamp: 2015-10-13T10:13:11Z + labels: + test: missing-route-port + name: frontend + resourceVersion: "259" + uid: 024d82eb-7193-11e5-b84d-080027c5bfa9 + spec: + clusterIP: 172.30.182.32 + portalIP: 172.30.182.32 + ports: + - name: web + port: 5432 + protocol: TCP + targetPort: 8080 + - name: web2 + port: 5433 + protocol: TCP + targetPort: 8080 + selector: + name: frontend + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} \ No newline at end of file