Skip to content

Commit

Permalink
Make expose use introspection to grab the port value if possible.
Browse files Browse the repository at this point in the history
Also improve service printing to include public IP addresses.
  • Loading branch information
brendandburns committed Apr 2, 2015
1 parent 92b6f49 commit 674efe6
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 11 deletions.
36 changes: 36 additions & 0 deletions pkg/kubectl/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"io"
"os"
"strconv"

"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
Expand Down Expand Up @@ -71,12 +72,24 @@ type Factory struct {
// PodSelectorForResource returns the pod selector associated with the provided resource name
// or an error.
PodSelectorForResource func(mapping *meta.RESTMapping, namespace, name string) (string, error)
// PortForResource returns the ports associated with the provided resource name or an error
PortsForResource func(mapping *meta.RESTMapping, namespace, name string) ([]string, error)
// Returns a schema that can validate objects stored on disk.
Validator func() (validation.Schema, error)
// Returns the default namespace to use in cases where no other namespace is specified
DefaultNamespace func() (string, error)
}

func getPorts(spec api.PodSpec) []string {
result := []string{}
for _, container := range spec.Containers {
for _, port := range container.Ports {
result = append(result, strconv.Itoa(port.ContainerPort))
}
}
return result
}

// NewFactory creates a factory with the default Kubernetes resources defined
// if optionalClientConfig is nil, then flags will be bound to a new clientcmd.ClientConfig.
// if optionalClientConfig is not nil, then this factory will make use of it.
Expand Down Expand Up @@ -168,6 +181,29 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
return "", fmt.Errorf("it is not possible to get a pod selector from %s", mapping.Kind)
}
},
PortsForResource: func(mapping *meta.RESTMapping, namespace, name string) ([]string, error) {
// TODO: replace with a swagger schema based approach (identify pod selector via schema introspection)
client, err := clients.ClientForVersion("")
if err != nil {
return nil, err
}
switch mapping.Kind {
case "ReplicationController":
rc, err := client.ReplicationControllers(namespace).Get(name)
if err != nil {
return nil, err
}
return getPorts(rc.Spec.Template.Spec), nil
case "Pod":
pod, err := client.Pods(namespace).Get(name)
if err != nil {
return nil, err
}
return getPorts(pod.Spec), nil
default:
return nil, fmt.Errorf("it is not possible to get ports from %s", mapping.Kind)
}
},
Resizer: func(mapping *meta.RESTMapping) (kubectl.Resizer, error) {
client, err := clients.ClientForVersion(mapping.APIVersion)
if err != nil {
Expand Down
28 changes: 20 additions & 8 deletions pkg/kubectl/cmd/expose.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,14 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err
if !found {
return util.UsageError(cmd, fmt.Sprintf("generator %q not found.", generator))
}
if util.GetFlagInt(cmd, "port") < 1 {
return util.UsageError(cmd, "--port is required and must be a positive integer.")
}
names := generator.ParamNames()
params := kubectl.MakeParams(cmd, names)
if len(util.GetFlagString(cmd, "service-name")) == 0 {
params["name"] = name
} else {
params["name"] = util.GetFlagString(cmd, "service-name")
}
if s, found := params["selector"]; !found || len(s) == 0 {
if s, found := params["selector"]; !found || len(s) == 0 || util.GetFlagInt(cmd, "port") < 1 {
mapper, _ := f.Object()
v, k, err := mapper.VersionAndKindForResource(resource)
if err != nil {
Expand All @@ -113,11 +110,26 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err
if err != nil {
return err
}
s, err := f.PodSelectorForResource(mapping, namespace, name)
if err != nil {
return err
if len(s) == 0 {
s, err := f.PodSelectorForResource(mapping, namespace, name)
if err != nil {
return err
}
params["selector"] = s
}
if util.GetFlagInt(cmd, "port") < 0 {
ports, err := f.PortsForResource(mapping, namespace, name)
if err != nil {
return err
}
if len(ports) == 0 {
return util.UsageError(cmd, "couldn't find a suitable port via --port flag or introspection")
}
if len(ports) > 1 {
return util.UsageError(cmd, "more than one port to choose from, please explicitly specify a port using the --port flag.")
}
params["port"] = ports[0]
}
params["selector"] = s
}
if util.GetFlagBool(cmd, "create-external-load-balancer") {
params["create-external-load-balancer"] = "true"
Expand Down
23 changes: 20 additions & 3 deletions pkg/kubectl/resource_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,13 +390,30 @@ func printReplicationControllerList(list *api.ReplicationControllerList, w io.Wr
}

func printService(svc *api.Service, w io.Writer) error {
ips := []string{svc.Spec.PortalIP}
for _, publicIP := range svc.Spec.PublicIPs {
ips = append(ips, publicIP)
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d/%s\n", svc.Name, formatLabels(svc.Labels),
formatLabels(svc.Spec.Selector), svc.Spec.PortalIP, svc.Spec.Ports[0].Port, svc.Spec.Ports[0].Protocol); err != nil {
formatLabels(svc.Spec.Selector), ips[0], svc.Spec.Ports[0].Port, svc.Spec.Ports[0].Protocol); err != nil {
return err
}
for i := 1; i < len(svc.Spec.Ports); i++ {

count := len(svc.Spec.Ports)
if len(ips) > count {
count = len(ips)
}
for i := 1; i < count; i++ {
ip := ""
if len(ips) > i {
ip = ips[i]
}
port := ""
if len(svc.Spec.Ports) > i {
port = fmt.Sprintf("%d/%s", svc.Spec.Ports[i].Port, svc.Spec.Ports[i].Protocol)
}
// Lay out additional ports.
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d/%s\n", "", "", "", "", svc.Spec.Ports[i].Port, svc.Spec.Ports[i].Protocol); err != nil {
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", "", "", "", ip, port); err != nil {
return err
}
}
Expand Down
116 changes: 116 additions & 0 deletions pkg/kubectl/resource_printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -630,3 +630,119 @@ func contains(fields []string, field string) bool {
}
return false
}

func TestPrintHumanReadableService(t *testing.T) {
tests := []api.Service{
{
Spec: api.ServiceSpec{
PortalIP: "1.2.3.4",
PublicIPs: []string{
"2.3.4.5",
"3.4.5.6",
},
Ports: []api.ServicePort{
{
Port: 80,
Protocol: "TCP",
},
},
},
},
{
Spec: api.ServiceSpec{
PortalIP: "1.2.3.4",
Ports: []api.ServicePort{
{
Port: 80,
Protocol: "TCP",
},
{
Port: 8090,
Protocol: "UDP",
},
{
Port: 8000,
Protocol: "TCP",
},
},
},
},
{
Spec: api.ServiceSpec{
PortalIP: "1.2.3.4",
PublicIPs: []string{
"2.3.4.5",
},
Ports: []api.ServicePort{
{
Port: 80,
Protocol: "TCP",
},
{
Port: 8090,
Protocol: "UDP",
},
{
Port: 8000,
Protocol: "TCP",
},
},
},
},
{
Spec: api.ServiceSpec{
PortalIP: "1.2.3.4",
PublicIPs: []string{
"2.3.4.5",
"4.5.6.7",
"5.6.7.8",
},
Ports: []api.ServicePort{
{
Port: 80,
Protocol: "TCP",
},
{
Port: 8090,
Protocol: "UDP",
},
{
Port: 8000,
Protocol: "TCP",
},
},
},
},
}

for _, svc := range tests {
buff := bytes.Buffer{}
printService(&svc, &buff)
output := string(buff.Bytes())
ip := svc.Spec.PortalIP
if !strings.Contains(output, ip) {
t.Errorf("expected to contain portal ip %s, but doesn't: %s", ip, output)
}

for _, ip = range svc.Spec.PublicIPs {
if !strings.Contains(output, ip) {
t.Errorf("expected to contain public ip %s, but doesn't: %s", ip, output)
}
}

for _, port := range svc.Spec.Ports {
portSpec := fmt.Sprintf("%d/%s", port.Port, port.Protocol)
if !strings.Contains(output, portSpec) {
t.Errorf("expected to contain port: %s, but doesn't: %s", portSpec, output)
}
}
// Max of # ports and (# public ip + portal ip)
count := len(svc.Spec.Ports)
if len(svc.Spec.PublicIPs)+1 > count {
count = len(svc.Spec.PublicIPs) + 1
}
if count != strings.Count(output, "\n") {
t.Errorf("expected %d newlines, found %d", count, strings.Count(output, "\n"))
}
}
}

0 comments on commit 674efe6

Please sign in to comment.