k9s/internal/resource/svc.go

198 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package resource
import (
"context"
"errors"
"sort"
"strconv"
"strings"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
)
const lbIPWidth = 16
// Service tracks a kubernetes resource.
type Service struct {
*Base
instance *v1.Service
}
// NewServiceList returns a new resource list.
func NewServiceList(c Connection, ns string) List {
return NewList(
ns,
"svc",
NewService(c),
AllVerbsAccess|DescribeAccess,
)
}
// NewService instantiates a new Service.
func NewService(c Connection) *Service {
s := &Service{&Base{Connection: c, Resource: k8s.NewService(c)}, nil}
s.Factory = s
return s
}
// New builds a new Service instance from a k8s resource.
func (r *Service) New(i interface{}) Columnar {
c := NewService(r.Connection)
switch instance := i.(type) {
case *v1.Service:
c.instance = instance
case v1.Service:
c.instance = &instance
default:
log.Fatal().Msgf("unknown Service type %#v", i)
}
c.path = c.namespacedName(c.instance.ObjectMeta)
return c
}
// Marshal resource to yaml.
// BOZO!! Why you need to fill type info??
func (r *Service) Marshal(path string) (string, error) {
ns, n := Namespaced(path)
i, err := r.Resource.Get(ns, n)
if err != nil {
return "", err
}
svc := i.(*v1.Service)
svc.TypeMeta.APIVersion = "v1"
svc.TypeMeta.Kind = "Service"
return r.marshalObject(svc)
}
// Logs tail logs for all pods represented by this service.
func (r *Service) Logs(ctx context.Context, c chan<- string, opts LogOptions) error {
instance, err := r.Resource.Get(opts.Namespace, opts.Name)
if err != nil {
return err
}
svc := instance.(*v1.Service)
log.Debug().Msgf("Service %s--%s", svc.Name, svc.Spec.Selector)
if len(svc.Spec.Selector) == 0 {
return errors.New("No logs for headless service")
}
return r.podLogs(ctx, c, svc.Spec.Selector, opts)
}
// Header returns resource header.
func (*Service) Header(ns string) Row {
hh := Row{}
if ns == AllNamespaces {
hh = append(hh, "NAMESPACE")
}
return append(hh,
"NAME",
"TYPE",
"CLUSTER-IP",
"EXTERNAL-IP",
"SELECTOR",
"PORTS",
"AGE",
)
}
// Fields retrieves displayable fields.
func (r *Service) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance
if ns == AllNamespaces {
ff = append(ff, i.Namespace)
}
return append(ff,
i.ObjectMeta.Name,
string(i.Spec.Type),
i.Spec.ClusterIP,
r.toIPs(i.Spec.Type, r.getSvcExtIPS(i)),
mapToStr(i.Spec.Selector),
r.toPorts(i.Spec.Ports),
toAge(i.ObjectMeta.CreationTimestamp),
)
}
// ----------------------------------------------------------------------------
// Helpers...
func (r *Service) getSvcExtIPS(svc *v1.Service) []string {
results := []string{}
switch svc.Spec.Type {
case v1.ServiceTypeClusterIP:
fallthrough
case v1.ServiceTypeNodePort:
return svc.Spec.ExternalIPs
case v1.ServiceTypeLoadBalancer:
lbIps := r.lbIngressIP(svc.Status.LoadBalancer)
if len(svc.Spec.ExternalIPs) > 0 {
if len(lbIps) > 0 {
results = append(results, lbIps)
}
return append(results, svc.Spec.ExternalIPs...)
}
if len(lbIps) > 0 {
results = append(results, lbIps)
}
case v1.ServiceTypeExternalName:
results = append(results, svc.Spec.ExternalName)
}
return results
}
func (*Service) lbIngressIP(s v1.LoadBalancerStatus) string {
ingress := s.Ingress
result := []string{}
for i := range ingress {
if len(ingress[i].IP) > 0 {
result = append(result, ingress[i].IP)
} else if len(ingress[i].Hostname) > 0 {
result = append(result, ingress[i].Hostname)
}
}
return strings.Join(result, ",")
}
func (*Service) toIPs(svcType v1.ServiceType, ips []string) string {
if len(ips) == 0 {
if svcType == v1.ServiceTypeLoadBalancer {
return "<pending>"
}
return MissingValue
}
sort.Strings(ips)
return strings.Join(ips, ",")
}
func (*Service) toPorts(pp []v1.ServicePort) string {
ports := make([]string, len(pp))
for i, p := range pp {
if len(p.Name) > 0 {
ports[i] = p.Name + ":"
}
ports[i] += strconv.Itoa(int(p.Port)) +
"►" +
strconv.Itoa(int(p.NodePort))
if p.Protocol != "TCP" {
ports[i] += "" + string(p.Protocol)
}
}
return strings.Join(ports, " ")
}