package cli import ( "context" models "github.com/portainer/portainer/api/http/models/kubernetes" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/intstr" ) // GetServices gets all the services for a given namespace in a k8s endpoint. func (kcl *KubeClient) GetServices(namespace string, lookupApplications bool) ([]models.K8sServiceInfo, error) { client := kcl.cli.CoreV1().Services(namespace) services, err := client.List(context.Background(), metav1.ListOptions{}) if err != nil { return nil, err } var result []models.K8sServiceInfo for _, service := range services.Items { servicePorts := make([]models.K8sServicePort, 0) for _, port := range service.Spec.Ports { servicePorts = append(servicePorts, models.K8sServicePort{ Name: port.Name, NodePort: int(port.NodePort), Port: int(port.Port), Protocol: string(port.Protocol), TargetPort: port.TargetPort.String(), }) } ingressStatus := make([]models.K8sServiceIngress, 0) for _, status := range service.Status.LoadBalancer.Ingress { ingressStatus = append(ingressStatus, models.K8sServiceIngress{ IP: status.IP, Host: status.Hostname, }) } var applications []models.K8sApplication if lookupApplications { applications, _ = kcl.getOwningApplication(namespace, service.Spec.Selector) } result = append(result, models.K8sServiceInfo{ Name: service.Name, UID: string(service.GetUID()), Type: string(service.Spec.Type), Namespace: service.Namespace, CreationTimestamp: service.GetCreationTimestamp().String(), AllocateLoadBalancerNodePorts: service.Spec.AllocateLoadBalancerNodePorts, Ports: servicePorts, IngressStatus: ingressStatus, Labels: service.GetLabels(), Annotations: service.GetAnnotations(), ClusterIPs: service.Spec.ClusterIPs, ExternalName: service.Spec.ExternalName, ExternalIPs: service.Spec.ExternalIPs, Applications: applications, }) } return result, nil } // CreateService creates a new service in a given namespace in a k8s endpoint. func (kcl *KubeClient) CreateService(namespace string, info models.K8sServiceInfo) error { ServiceClient := kcl.cli.CoreV1().Services(namespace) var service v1.Service service.Name = info.Name service.Spec.Type = v1.ServiceType(info.Type) service.Namespace = info.Namespace service.Annotations = info.Annotations service.Labels = info.Labels service.Spec.AllocateLoadBalancerNodePorts = info.AllocateLoadBalancerNodePorts service.Spec.Selector = info.Selector // Set ports. for _, p := range info.Ports { var port v1.ServicePort port.Name = p.Name port.NodePort = int32(p.NodePort) port.Port = int32(p.Port) port.Protocol = v1.Protocol(p.Protocol) port.TargetPort = intstr.FromString(p.TargetPort) service.Spec.Ports = append(service.Spec.Ports, port) } // Set ingresses. for _, i := range info.IngressStatus { var ing v1.LoadBalancerIngress ing.IP = i.IP ing.Hostname = i.Host service.Status.LoadBalancer.Ingress = append( service.Status.LoadBalancer.Ingress, ing, ) } _, err := ServiceClient.Create(context.Background(), &service, metav1.CreateOptions{}) return err } // DeleteServices processes a K8sServiceDeleteRequest by deleting each service // in its given namespace. func (kcl *KubeClient) DeleteServices(reqs models.K8sServiceDeleteRequests) error { var err error for namespace := range reqs { for _, service := range reqs[namespace] { serviceClient := kcl.cli.CoreV1().Services(namespace) err = serviceClient.Delete( context.Background(), service, metav1.DeleteOptions{}, ) } } return err } // UpdateService updates service in a given namespace in a k8s endpoint. func (kcl *KubeClient) UpdateService(namespace string, info models.K8sServiceInfo) error { ServiceClient := kcl.cli.CoreV1().Services(namespace) var service v1.Service service.Name = info.Name service.Spec.Type = v1.ServiceType(info.Type) service.Namespace = info.Namespace service.Annotations = info.Annotations service.Labels = info.Labels service.Spec.AllocateLoadBalancerNodePorts = info.AllocateLoadBalancerNodePorts service.Spec.Selector = info.Selector // Set ports. for _, p := range info.Ports { var port v1.ServicePort port.Name = p.Name port.NodePort = int32(p.NodePort) port.Port = int32(p.Port) port.Protocol = v1.Protocol(p.Protocol) port.TargetPort = intstr.FromString(p.TargetPort) service.Spec.Ports = append(service.Spec.Ports, port) } // Set ingresses. for _, i := range info.IngressStatus { var ing v1.LoadBalancerIngress ing.IP = i.IP ing.Hostname = i.Host service.Status.LoadBalancer.Ingress = append( service.Status.LoadBalancer.Ingress, ing, ) } _, err := ServiceClient.Update(context.Background(), &service, metav1.UpdateOptions{}) return err } // getOwningApplication gets the application that owns the given service selector. func (kcl *KubeClient) getOwningApplication(namespace string, selector map[string]string) ([]models.K8sApplication, error) { if len(selector) == 0 { return nil, nil } selectorLabels := labels.SelectorFromSet(selector).String() // look for replicasets first, limit 1 (we only support one owner) replicasets, err := kcl.cli.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selectorLabels, Limit: 1}) if err != nil { return nil, err } var meta metav1.Object if replicasets != nil && len(replicasets.Items) > 0 { meta = replicasets.Items[0].GetObjectMeta() } else { // otherwise look for matching pods, limit 1 (we only support one owner) pods, err := kcl.cli.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selectorLabels, Limit: 1}) if err != nil { return nil, err } if pods == nil || len(pods.Items) == 0 { return nil, nil } meta = pods.Items[0].GetObjectMeta() } return makeApplication(meta), nil } func makeApplication(meta metav1.Object) []models.K8sApplication { ownerReferences := meta.GetOwnerReferences() if len(ownerReferences) == 0 { return nil } // Currently, we only support one owner reference ownerReference := ownerReferences[0] return []models.K8sApplication{ { // Only the name is used right now, but we can add more fields in the future Name: ownerReference.Name, }, } }