2022-09-21 04:49:42 +00:00
package cli
import (
"context"
2024-10-01 01:15:51 +00:00
"fmt"
2022-09-21 04:49:42 +00:00
2022-11-13 19:33:57 +00:00
models "github.com/portainer/portainer/api/http/models/kubernetes"
2024-10-01 01:15:51 +00:00
"github.com/rs/zerolog/log"
2024-06-26 21:14:22 +00:00
2024-10-01 01:15:51 +00:00
corev1 "k8s.io/api/core/v1"
2022-09-21 04:49:42 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
2024-10-01 01:15:51 +00:00
// GetServices gets all the services for either at the cluster level or a given namespace in a k8s endpoint.
// It returns a list of K8sServiceInfo objects.
func ( kcl * KubeClient ) GetServices ( namespace string ) ( [ ] models . K8sServiceInfo , error ) {
if kcl . IsKubeAdmin {
return kcl . fetchServices ( namespace )
}
return kcl . fetchServicesForNonAdmin ( namespace )
}
// fetchServicesForNonAdmin gets all the services for either at the cluster level or a given namespace in a k8s endpoint.
// the namespace will be coming from NonAdminNamespaces as non-admin users are restricted to certain namespaces.
// it returns a list of K8sServiceInfo objects.
func ( kcl * KubeClient ) fetchServicesForNonAdmin ( namespace string ) ( [ ] models . K8sServiceInfo , error ) {
log . Debug ( ) . Msgf ( "Fetching services for non-admin user: %v" , kcl . NonAdminNamespaces )
if len ( kcl . NonAdminNamespaces ) == 0 {
return nil , nil
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
services , err := kcl . fetchServices ( namespace )
2022-09-21 04:49:42 +00:00
if err != nil {
return nil , err
}
2024-10-01 01:15:51 +00:00
nonAdminNamespaceSet := kcl . buildNonAdminNamespacesMap ( )
results := make ( [ ] models . K8sServiceInfo , 0 )
for _ , service := range services {
if _ , ok := nonAdminNamespaceSet [ service . Namespace ] ; ok {
results = append ( results , service )
}
}
return results , nil
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
// fetchServices gets the services in a given namespace in a k8s endpoint.
// It returns a list of K8sServiceInfo objects.
func ( kcl * KubeClient ) fetchServices ( namespace string ) ( [ ] models . K8sServiceInfo , error ) {
services , err := kcl . cli . CoreV1 ( ) . Services ( namespace ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
if err != nil {
return nil , err
}
results := make ( [ ] models . K8sServiceInfo , 0 )
2022-09-21 04:49:42 +00:00
for _ , service := range services . Items {
2024-10-01 01:15:51 +00:00
results = append ( results , parseService ( service ) )
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
return results , nil
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
// parseService converts a k8s native service object to a Portainer K8sServiceInfo object.
// service ports, ingress status, labels, annotations, cluster IPs, and external IPs are parsed.
// it returns a K8sServiceInfo object.
func parseService ( service corev1 . Service ) models . K8sServiceInfo {
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 ( ) ,
} )
}
2023-03-02 19:45:19 +00:00
2024-10-01 01:15:51 +00:00
ingressStatus := make ( [ ] models . K8sServiceIngress , 0 )
for _ , status := range service . Status . LoadBalancer . Ingress {
ingressStatus = append ( ingressStatus , models . K8sServiceIngress {
IP : status . IP ,
Host : status . Hostname ,
2022-09-21 04:49:42 +00:00
} )
}
2024-10-01 01:15:51 +00:00
return models . K8sServiceInfo {
Name : service . Name ,
UID : string ( service . GetUID ( ) ) ,
Type : string ( service . Spec . Type ) ,
Namespace : service . Namespace ,
CreationDate : 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 ,
Selector : service . Spec . Selector ,
}
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
// convertToK8sService converts a K8sServiceInfo object back to a k8s native service object.
// this is required for create and update operations.
// it returns a v1.Service object.
func ( kcl * KubeClient ) convertToK8sService ( info models . K8sServiceInfo ) corev1 . Service {
service := corev1 . Service { }
2022-09-21 04:49:42 +00:00
service . Name = info . Name
2024-10-01 01:15:51 +00:00
service . Spec . Type = corev1 . ServiceType ( info . Type )
2022-09-21 04:49:42 +00:00
service . Namespace = info . Namespace
service . Annotations = info . Annotations
service . Labels = info . Labels
service . Spec . AllocateLoadBalancerNodePorts = info . AllocateLoadBalancerNodePorts
service . Spec . Selector = info . Selector
for _ , p := range info . Ports {
2024-10-01 01:15:51 +00:00
port := corev1 . ServicePort { }
2022-09-21 04:49:42 +00:00
port . Name = p . Name
port . NodePort = int32 ( p . NodePort )
port . Port = int32 ( p . Port )
2024-10-01 01:15:51 +00:00
port . Protocol = corev1 . Protocol ( p . Protocol )
2023-03-02 19:45:19 +00:00
port . TargetPort = intstr . FromString ( p . TargetPort )
2022-09-21 04:49:42 +00:00
service . Spec . Ports = append ( service . Spec . Ports , port )
}
for _ , i := range info . IngressStatus {
service . Status . LoadBalancer . Ingress = append (
service . Status . LoadBalancer . Ingress ,
2024-10-01 01:15:51 +00:00
corev1 . LoadBalancerIngress { IP : i . IP , Hostname : i . Host } ,
2022-09-21 04:49:42 +00:00
)
}
2024-06-26 21:14:22 +00:00
return service
}
// CreateService creates a new service in a given namespace in a k8s endpoint.
func ( kcl * KubeClient ) CreateService ( namespace string , info models . K8sServiceInfo ) error {
2024-10-01 01:15:51 +00:00
service := kcl . convertToK8sService ( info )
_ , err := kcl . cli . CoreV1 ( ) . Services ( namespace ) . Create ( context . Background ( ) , & service , metav1 . CreateOptions { } )
2022-09-21 04:49:42 +00:00
return err
}
// DeleteServices processes a K8sServiceDeleteRequest by deleting each service
// in its given namespace.
func ( kcl * KubeClient ) DeleteServices ( reqs models . K8sServiceDeleteRequests ) error {
for namespace := range reqs {
for _ , service := range reqs [ namespace ] {
2024-10-01 01:15:51 +00:00
err := kcl . cli . CoreV1 ( ) . Services ( namespace ) . Delete ( context . Background ( ) , service , metav1 . DeleteOptions { } )
if err != nil {
return err
}
2022-09-21 04:49:42 +00:00
}
}
2024-06-26 21:14:22 +00:00
2024-10-01 01:15:51 +00:00
return nil
2022-09-21 04:49:42 +00:00
}
// UpdateService updates service in a given namespace in a k8s endpoint.
func ( kcl * KubeClient ) UpdateService ( namespace string , info models . K8sServiceInfo ) error {
2024-10-01 01:15:51 +00:00
service := kcl . convertToK8sService ( info )
_ , err := kcl . cli . CoreV1 ( ) . Services ( namespace ) . Update ( context . Background ( ) , & service , metav1 . UpdateOptions { } )
2022-09-21 04:49:42 +00:00
return err
}
2023-03-02 19:45:19 +00:00
2024-10-01 01:15:51 +00:00
// CombineServicesWithApplications retrieves applications based on service selectors in a given namespace
// for all services, it lists pods based on the service selector and converts the pod to an application
// if replicasets are found, it updates the owner reference to deployment
// it then combines the service with the application
// finally, it returns a list of K8sServiceInfo objects
func ( kcl * KubeClient ) CombineServicesWithApplications ( services [ ] models . K8sServiceInfo ) ( [ ] models . K8sServiceInfo , error ) {
if containsServiceWithSelector ( services ) {
updatedServices := make ( [ ] models . K8sServiceInfo , len ( services ) )
pods , replicaSets , _ , _ , _ , _ , err := kcl . fetchAllPodsAndReplicaSets ( "" , metav1 . ListOptions { } )
if err != nil {
return nil , fmt . Errorf ( "an error occurred during the CombineServicesWithApplications operation, unable to fetch pods and replica sets. Error: %w" , err )
}
2023-03-02 19:45:19 +00:00
2024-10-01 01:15:51 +00:00
for index , service := range services {
updatedService := service
2023-03-02 19:45:19 +00:00
2024-10-01 01:15:51 +00:00
application , err := kcl . GetApplicationFromServiceSelector ( pods , service , replicaSets )
if err != nil {
return services , fmt . Errorf ( "an error occurred during the CombineServicesWithApplications operation, unable to get application from service. Error: %w" , err )
}
2023-03-02 19:45:19 +00:00
2024-10-01 01:15:51 +00:00
if application != nil {
updatedService . Applications = append ( updatedService . Applications , * application )
}
2023-03-02 19:45:19 +00:00
2024-10-01 01:15:51 +00:00
updatedServices [ index ] = updatedService
2023-03-02 19:45:19 +00:00
}
2024-10-01 01:15:51 +00:00
return updatedServices , nil
2023-03-02 19:45:19 +00:00
}
2024-10-01 01:15:51 +00:00
return services , nil
2023-03-02 19:45:19 +00:00
}
2024-10-01 01:15:51 +00:00
// containsServiceWithSelector checks if a list of services contains a service with a selector
// it returns true if any service has a selector, otherwise false
func containsServiceWithSelector ( services [ ] models . K8sServiceInfo ) bool {
for _ , service := range services {
if len ( service . Selector ) > 0 {
return true
}
2023-03-02 19:45:19 +00:00
}
2024-10-01 01:15:51 +00:00
return false
}
2023-03-02 19:45:19 +00:00
2024-10-01 01:15:51 +00:00
// buildServicesMap builds a map of service names from a list of K8sServiceInfo objects
// it returns a map of service names for lookups
func ( kcl * KubeClient ) buildServicesMap ( services [ ] models . K8sServiceInfo ) map [ string ] struct { } {
serviceMap := make ( map [ string ] struct { } )
for _ , service := range services {
serviceMap [ service . Name ] = struct { } { }
2023-03-02 19:45:19 +00:00
}
2024-10-01 01:15:51 +00:00
return serviceMap
2023-03-02 19:45:19 +00:00
}