mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Make expose use introspection to grab the port value if possible.
Also improve service printing to include public IP addresses.
This commit is contained in:
parent
92b6f49b3c
commit
674efe6de8
@ -20,6 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
@ -71,12 +72,24 @@ type Factory struct {
|
|||||||
// PodSelectorForResource returns the pod selector associated with the provided resource name
|
// PodSelectorForResource returns the pod selector associated with the provided resource name
|
||||||
// or an error.
|
// or an error.
|
||||||
PodSelectorForResource func(mapping *meta.RESTMapping, namespace, name string) (string, 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.
|
// Returns a schema that can validate objects stored on disk.
|
||||||
Validator func() (validation.Schema, error)
|
Validator func() (validation.Schema, error)
|
||||||
// Returns the default namespace to use in cases where no other namespace is specified
|
// Returns the default namespace to use in cases where no other namespace is specified
|
||||||
DefaultNamespace func() (string, error)
|
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
|
// 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 nil, then flags will be bound to a new clientcmd.ClientConfig.
|
||||||
// if optionalClientConfig is not nil, then this factory will make use of it.
|
// if optionalClientConfig is not nil, then this factory will make use of it.
|
||||||
@ -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)
|
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) {
|
Resizer: func(mapping *meta.RESTMapping) (kubectl.Resizer, error) {
|
||||||
client, err := clients.ClientForVersion(mapping.APIVersion)
|
client, err := clients.ClientForVersion(mapping.APIVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,9 +93,6 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err
|
|||||||
if !found {
|
if !found {
|
||||||
return util.UsageError(cmd, fmt.Sprintf("generator %q not found.", generator))
|
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()
|
names := generator.ParamNames()
|
||||||
params := kubectl.MakeParams(cmd, names)
|
params := kubectl.MakeParams(cmd, names)
|
||||||
if len(util.GetFlagString(cmd, "service-name")) == 0 {
|
if len(util.GetFlagString(cmd, "service-name")) == 0 {
|
||||||
@ -103,7 +100,7 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err
|
|||||||
} else {
|
} else {
|
||||||
params["name"] = util.GetFlagString(cmd, "service-name")
|
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()
|
mapper, _ := f.Object()
|
||||||
v, k, err := mapper.VersionAndKindForResource(resource)
|
v, k, err := mapper.VersionAndKindForResource(resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -113,11 +110,26 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s, err := f.PodSelectorForResource(mapping, namespace, name)
|
if len(s) == 0 {
|
||||||
if err != nil {
|
s, err := f.PodSelectorForResource(mapping, namespace, name)
|
||||||
return err
|
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") {
|
if util.GetFlagBool(cmd, "create-external-load-balancer") {
|
||||||
params["create-external-load-balancer"] = "true"
|
params["create-external-load-balancer"] = "true"
|
||||||
|
@ -390,13 +390,30 @@ func printReplicationControllerList(list *api.ReplicationControllerList, w io.Wr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printService(svc *api.Service, w io.Writer) error {
|
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),
|
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
|
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.
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -630,3 +630,119 @@ func contains(fields []string, field string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user