Commit fa5556b9 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #32811 from fraenkel/headless_service

Automatic merge from submit-queue (batch tested with PRs 38260, 32811, 28458, 33570, 37096) Allow no ports when exposing headless service fixes #32795
parents 99c066ef a3537a02
...@@ -177,6 +177,8 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri ...@@ -177,6 +177,8 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
params["selector"] = s params["selector"] = s
} }
isHeadlessService := params["cluster-ip"] == "None"
// For objects that need a port, derive it from the exposed object in case a user // For objects that need a port, derive it from the exposed object in case a user
// didn't explicitly specify one via --port // didn't explicitly specify one via --port
if port, found := params["port"]; found && kubectl.IsZero(port) { if port, found := params["port"]; found && kubectl.IsZero(port) {
...@@ -186,7 +188,9 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri ...@@ -186,7 +188,9 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
} }
switch len(ports) { switch len(ports) {
case 0: case 0:
return cmdutil.UsageError(cmd, "couldn't find port via --port flag or introspection") if !isHeadlessService {
return cmdutil.UsageError(cmd, "couldn't find port via --port flag or introspection")
}
case 1: case 1:
params["port"] = ports[0] params["port"] = ports[0]
default: default:
......
...@@ -265,6 +265,32 @@ func TestRunExposeService(t *testing.T) { ...@@ -265,6 +265,32 @@ func TestRunExposeService(t *testing.T) {
status: 200, status: 200,
}, },
{ {
name: "expose-headless-service-no-port",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{},
Selector: map[string]string{"func": "stream"},
ClusterIP: api.ClusterIPNone,
},
},
expected: "service \"foo\" exposed",
status: 200,
},
{
name: "expose-from-file", name: "expose-from-file",
args: []string{}, args: []string{},
ns: "test", ns: "test",
......
...@@ -110,6 +110,9 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) { ...@@ -110,6 +110,9 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) {
return nil, fmt.Errorf("'name' is a required parameter.") return nil, fmt.Errorf("'name' is a required parameter.")
} }
} }
isHeadlessService := params["cluster-ip"] == "None"
ports := []api.ServicePort{} ports := []api.ServicePort{}
servicePortName, found := params["port-name"] servicePortName, found := params["port-name"]
if !found { if !found {
...@@ -131,44 +134,46 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) { ...@@ -131,44 +134,46 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) {
var portString string var portString string
if portString, found = params["ports"]; !found { if portString, found = params["ports"]; !found {
portString, found = params["port"] portString, found = params["port"]
if !found { if !found && !isHeadlessService {
return nil, fmt.Errorf("'port' is a required parameter.") return nil, fmt.Errorf("'port' is a required parameter.")
} }
} }
portStringSlice := strings.Split(portString, ",") if portString != "" {
for i, stillPortString := range portStringSlice { portStringSlice := strings.Split(portString, ",")
port, err := strconv.Atoi(stillPortString) for i, stillPortString := range portStringSlice {
if err != nil { port, err := strconv.Atoi(stillPortString)
return nil, err if err != nil {
} return nil, err
name := servicePortName }
// If we are going to assign multiple ports to a service, we need to name := servicePortName
// generate a different name for each one. // If we are going to assign multiple ports to a service, we need to
if len(portStringSlice) > 1 { // generate a different name for each one.
name = fmt.Sprintf("port-%d", i+1) if len(portStringSlice) > 1 {
} name = fmt.Sprintf("port-%d", i+1)
protocol := params["protocol"] }
protocol := params["protocol"]
switch {
case len(protocol) == 0 && len(portProtocolMap) == 0: switch {
// Default to TCP, what the flag was doing previously. case len(protocol) == 0 && len(portProtocolMap) == 0:
protocol = "TCP" // Default to TCP, what the flag was doing previously.
case len(protocol) > 0 && len(portProtocolMap) > 0: protocol = "TCP"
// User has specified the --protocol while exposing a multiprotocol resource case len(protocol) > 0 && len(portProtocolMap) > 0:
// We should stomp multiple protocols with the one specified ie. do nothing // User has specified the --protocol while exposing a multiprotocol resource
case len(protocol) == 0 && len(portProtocolMap) > 0: // We should stomp multiple protocols with the one specified ie. do nothing
// no --protocol and we expose a multiprotocol resource case len(protocol) == 0 && len(portProtocolMap) > 0:
protocol = "TCP" // have the default so we can stay sane // no --protocol and we expose a multiprotocol resource
if exposeProtocol, found := portProtocolMap[stillPortString]; found { protocol = "TCP" // have the default so we can stay sane
protocol = exposeProtocol if exposeProtocol, found := portProtocolMap[stillPortString]; found {
protocol = exposeProtocol
}
} }
ports = append(ports, api.ServicePort{
Name: name,
Port: int32(port),
Protocol: api.Protocol(protocol),
})
} }
ports = append(ports, api.ServicePort{
Name: name,
Port: int32(port),
Protocol: api.Protocol(protocol),
})
} }
service := api.Service{ service := api.Service{
......
...@@ -536,6 +536,29 @@ func TestGenerateService(t *testing.T) { ...@@ -536,6 +536,29 @@ func TestGenerateService(t *testing.T) {
}, },
}, },
}, },
{
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"protocol": "TCP",
"container-port": "1234",
"cluster-ip": "None",
},
expected: api.Service{
ObjectMeta: api.ObjectMeta{
Name: "test",
},
Spec: api.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []api.ServicePort{},
ClusterIP: api.ClusterIPNone,
},
},
},
} }
for _, test := range tests { for _, test := range tests {
obj, err := test.generator.Generate(test.params) obj, err := test.generator.Generate(test.params)
......
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