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
"strings"
2022-11-13 19:33:57 +00:00
models "github.com/portainer/portainer/api/http/models/kubernetes"
2022-12-20 03:46:51 +00:00
"github.com/portainer/portainer/api/stacks/stackutils"
2022-10-17 21:46:27 +00:00
"github.com/rs/zerolog/log"
2022-09-21 04:49:42 +00:00
netv1 "k8s.io/api/networking/v1"
2024-10-01 01:15:51 +00:00
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2022-09-21 04:49:42 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
2022-10-17 21:46:27 +00:00
func ( kcl * KubeClient ) GetIngressControllers ( ) ( models . K8sIngressControllers , error ) {
2024-10-01 01:15:51 +00:00
classeses , err := kcl . cli . NetworkingV1 ( ) . IngressClasses ( ) . List ( context . Background ( ) , metav1 . ListOptions { } )
2022-09-21 04:49:42 +00:00
if err != nil {
2022-10-17 21:46:27 +00:00
return nil , err
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
ingresses , err := kcl . GetIngresses ( "" )
2022-09-26 19:43:24 +00:00
if err != nil {
2022-10-17 21:46:27 +00:00
return nil , err
2022-09-26 19:43:24 +00:00
}
2024-10-01 01:15:51 +00:00
2022-09-26 19:43:24 +00:00
usedClasses := make ( map [ string ] struct { } )
for _ , ingress := range ingresses {
usedClasses [ ingress . ClassName ] = struct { } { }
}
2024-10-01 01:15:51 +00:00
results := [ ] models . K8sIngressController { }
for _ , class := range classeses . Items {
ingressClass := parseIngressClass ( class )
2022-09-26 19:43:24 +00:00
if _ , ok := usedClasses [ class . Name ] ; ok {
2024-10-01 01:15:51 +00:00
ingressClass . Used = true
2022-09-26 19:43:24 +00:00
}
2024-10-01 01:15:51 +00:00
results = append ( results , ingressClass )
}
return results , nil
}
// fetchIngressClasses fetches all the ingress classes in a k8s endpoint.
func ( kcl * KubeClient ) fetchIngressClasses ( ) ( [ ] models . K8sIngressController , error ) {
ingressClasses , err := kcl . cli . NetworkingV1 ( ) . IngressClasses ( ) . List ( context . Background ( ) , metav1 . ListOptions { } )
if err != nil {
return nil , err
}
var controllers [ ] models . K8sIngressController
for _ , ingressClass := range ingressClasses . Items {
controllers = append ( controllers , parseIngressClass ( ingressClass ) )
2022-09-21 04:49:42 +00:00
}
2022-10-17 21:46:27 +00:00
return controllers , nil
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
// parseIngressClass converts a k8s native ingress class object to a Portainer K8sIngressController object.
func parseIngressClass ( ingressClasses netv1 . IngressClass ) models . K8sIngressController {
ingressContoller := models . K8sIngressController {
Name : ingressClasses . Spec . Controller ,
ClassName : ingressClasses . Name ,
}
switch {
case strings . Contains ( ingressContoller . Name , "nginx" ) :
ingressContoller . Type = "nginx"
case strings . Contains ( ingressContoller . Name , "traefik" ) :
ingressContoller . Type = "traefik"
default :
ingressContoller . Type = "other"
}
return ingressContoller
}
// GetIngress gets an ingress in a given namespace in a k8s endpoint.
func ( kcl * KubeClient ) GetIngress ( namespace , ingressName string ) ( models . K8sIngressInfo , error ) {
ingress , err := kcl . cli . NetworkingV1 ( ) . Ingresses ( namespace ) . Get ( context . Background ( ) , ingressName , metav1 . GetOptions { } )
if err != nil {
return models . K8sIngressInfo { } , err
}
return parseIngress ( * ingress ) , nil
}
2022-09-21 04:49:42 +00:00
// GetIngresses gets all the ingresses for a given namespace in a k8s endpoint.
func ( kcl * KubeClient ) GetIngresses ( namespace string ) ( [ ] models . K8sIngressInfo , error ) {
2024-10-01 01:15:51 +00:00
if kcl . IsKubeAdmin {
return kcl . fetchIngresses ( namespace )
}
return kcl . fetchIngressesForNonAdmin ( namespace )
}
// fetchIngressesForNonAdmin gets all the ingresses for non-admin users in a k8s endpoint.
func ( kcl * KubeClient ) fetchIngressesForNonAdmin ( namespace string ) ( [ ] models . K8sIngressInfo , error ) {
log . Debug ( ) . Msgf ( "Fetching ingresses for non-admin user: %v" , kcl . NonAdminNamespaces )
if len ( kcl . NonAdminNamespaces ) == 0 {
return nil , nil
}
ingresses , err := kcl . fetchIngresses ( 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 . K8sIngressInfo , 0 )
for _ , ingress := range ingresses {
if _ , ok := nonAdminNamespaceSet [ ingress . Namespace ] ; ok {
results = append ( results , ingress )
}
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
return results , nil
}
// fetchIngresses fetches all the ingresses for a given namespace in a k8s endpoint.
func ( kcl * KubeClient ) fetchIngresses ( namespace string ) ( [ ] models . K8sIngressInfo , error ) {
ingresses , err := kcl . cli . NetworkingV1 ( ) . Ingresses ( namespace ) . List ( context . Background ( ) , metav1 . ListOptions { } )
2022-09-21 04:49:42 +00:00
if err != nil {
return nil , err
}
2024-10-01 01:15:51 +00:00
ingressClasses , err := kcl . fetchIngressClasses ( )
if err != nil {
return nil , err
}
results := [ ] models . K8sIngressInfo { }
if len ( ingresses . Items ) == 0 {
return results , nil
}
for _ , ingress := range ingresses . Items {
result := parseIngress ( ingress )
if ingress . Spec . IngressClassName != nil {
result . Type = findUsedIngressFromIngressClasses ( ingressClasses , * ingress . Spec . IngressClassName ) . Name
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
results = append ( results , result )
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
return results , nil
}
2022-10-24 20:41:30 +00:00
2024-10-01 01:15:51 +00:00
// parseIngress converts a k8s native ingress object to a Portainer K8sIngressInfo object.
func parseIngress ( ingress netv1 . Ingress ) models . K8sIngressInfo {
ingressClassName := ""
if ingress . Spec . IngressClassName != nil {
ingressClassName = * ingress . Spec . IngressClassName
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
result := models . K8sIngressInfo {
Name : ingress . Name ,
Namespace : ingress . Namespace ,
UID : string ( ingress . UID ) ,
Annotations : ingress . Annotations ,
Labels : ingress . Labels ,
CreationDate : ingress . CreationTimestamp . Time ,
ClassName : ingressClassName ,
}
for _ , tls := range ingress . Spec . TLS {
result . TLS = append ( result . TLS , models . K8sIngressTLS {
Hosts : tls . Hosts ,
SecretName : tls . SecretName ,
} )
}
hosts := make ( map [ string ] struct { } )
for _ , r := range ingress . Spec . Rules {
hosts [ r . Host ] = struct { } { }
if r . HTTP == nil {
continue
}
for _ , p := range r . HTTP . Paths {
var path models . K8sIngressPath
path . IngressName = result . Name
path . Host = r . Host
path . Path = p . Path
if p . PathType != nil {
path . PathType = string ( * p . PathType )
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
path . ServiceName = p . Backend . Service . Name
path . Port = int ( p . Backend . Service . Port . Number )
result . Paths = append ( result . Paths , path )
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
for host := range hosts {
result . Hosts = append ( result . Hosts , host )
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
return result
}
// findUsedIngressFromIngressClasses searches for an ingress in a slice of ingress classes and returns the ingress if found.
func findUsedIngressFromIngressClasses ( ingressClasses [ ] models . K8sIngressController , className string ) models . K8sIngressController {
for _ , ingressClass := range ingressClasses {
if ingressClass . ClassName == className {
return ingressClass
}
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
return models . K8sIngressController { }
2022-09-21 04:49:42 +00:00
}
// CreateIngress creates a new ingress in a given namespace in a k8s endpoint.
2022-12-20 03:46:51 +00:00
func ( kcl * KubeClient ) CreateIngress ( namespace string , info models . K8sIngressInfo , owner string ) error {
2024-10-01 01:15:51 +00:00
ingress := kcl . convertToK8sIngress ( info , owner )
_ , err := kcl . cli . NetworkingV1 ( ) . Ingresses ( namespace ) . Create ( context . Background ( ) , & ingress , metav1 . CreateOptions { } )
if err != nil {
return err
2022-10-24 20:41:30 +00:00
}
2024-10-01 01:15:51 +00:00
return nil
}
// convertToK8sIngress converts a Portainer K8sIngressInfo object to a k8s native Ingress object.
// this is required for create and update operations.
func ( kcl * KubeClient ) convertToK8sIngress ( info models . K8sIngressInfo , owner string ) netv1 . Ingress {
2024-10-01 01:43:46 +00:00
ingressSpec := netv1 . IngressSpec { }
if info . ClassName != "" {
ingressSpec . IngressClassName = & info . ClassName
}
2024-10-01 01:15:51 +00:00
result := netv1 . Ingress {
ObjectMeta : metav1 . ObjectMeta {
Name : info . Name ,
Namespace : info . Namespace ,
Annotations : info . Annotations ,
} ,
2024-10-01 01:43:46 +00:00
Spec : ingressSpec ,
2022-12-20 03:46:51 +00:00
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
labels := make ( map [ string ] string )
labels [ "io.portainer.kubernetes.ingress.owner" ] = stackutils . SanitizeLabel ( owner )
result . Labels = labels
tls := [ ] netv1 . IngressTLS { }
for _ , t := range info . TLS {
2022-09-21 04:49:42 +00:00
tls = append ( tls , netv1 . IngressTLS {
2024-10-01 01:15:51 +00:00
Hosts : t . Hosts ,
SecretName : t . SecretName ,
2022-09-21 04:49:42 +00:00
} )
}
2024-10-01 01:15:51 +00:00
result . Spec . TLS = tls
2022-09-21 04:49:42 +00:00
rules := make ( map [ string ] [ ] netv1 . HTTPIngressPath )
for _ , path := range info . Paths {
pathType := netv1 . PathType ( path . PathType )
rules [ path . Host ] = append ( rules [ path . Host ] , netv1 . HTTPIngressPath {
Path : path . Path ,
PathType : & pathType ,
Backend : netv1 . IngressBackend {
Service : & netv1 . IngressServiceBackend {
Name : path . ServiceName ,
Port : netv1 . ServiceBackendPort {
Number : int32 ( path . Port ) ,
} ,
} ,
} ,
} )
}
for rule , paths := range rules {
2024-10-01 01:15:51 +00:00
result . Spec . Rules = append ( result . Spec . Rules , netv1 . IngressRule {
2022-09-21 04:49:42 +00:00
Host : rule ,
IngressRuleValue : netv1 . IngressRuleValue {
HTTP : & netv1 . HTTPIngressRuleValue {
Paths : paths ,
} ,
} ,
} )
}
2023-06-14 01:45:25 +00:00
for _ , host := range info . Hosts {
if _ , ok := rules [ host ] ; ! ok {
2024-10-01 01:15:51 +00:00
result . Spec . Rules = append ( result . Spec . Rules , netv1 . IngressRule {
2023-06-13 00:38:00 +00:00
Host : host ,
2023-06-14 01:45:25 +00:00
} )
2023-06-13 00:38:00 +00:00
}
}
2024-10-01 01:15:51 +00:00
return result
2022-09-21 04:49:42 +00:00
}
// DeleteIngresses processes a K8sIngressDeleteRequest by deleting each ingress
// in its given namespace.
func ( kcl * KubeClient ) DeleteIngresses ( reqs models . K8sIngressDeleteRequests ) error {
for namespace := range reqs {
for _ , ingress := range reqs [ namespace ] {
2024-10-01 01:15:51 +00:00
err := kcl . cli . NetworkingV1 ( ) . Ingresses ( namespace ) . Delete (
2022-09-21 04:49:42 +00:00
context . Background ( ) ,
ingress ,
metav1 . DeleteOptions { } ,
)
2024-10-01 01:15:51 +00:00
if err != nil {
return err
}
2022-09-21 04:49:42 +00:00
}
}
2024-10-01 01:15:51 +00:00
return nil
2022-09-21 04:49:42 +00:00
}
// UpdateIngress updates an existing ingress in a given namespace in a k8s endpoint.
func ( kcl * KubeClient ) UpdateIngress ( namespace string , info models . K8sIngressInfo ) error {
2024-10-01 01:15:51 +00:00
ingress := kcl . convertToK8sIngress ( info , "" )
_ , err := kcl . cli . NetworkingV1 ( ) . Ingresses ( namespace ) . Update ( context . Background ( ) , & ingress , metav1 . UpdateOptions { } )
2024-02-05 03:30:36 +00:00
if err != nil {
return err
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
return nil
}
2022-09-21 04:49:42 +00:00
2024-10-01 01:15:51 +00:00
// CombineIngressWithService combines an ingress with a service that is being used by the ingress.
// this is required to display the service that is being used by the ingress in the UI edit view.
func ( kcl * KubeClient ) CombineIngressWithService ( ingress models . K8sIngressInfo ) ( models . K8sIngressInfo , error ) {
services , err := kcl . GetServices ( ingress . Namespace )
if err != nil {
return models . K8sIngressInfo { } , fmt . Errorf ( "an error occurred during the CombineIngressWithService operation, unable to retrieve services from the Kubernetes for a namespace level user. Error: %w" , err )
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
serviceMap := kcl . buildServicesMap ( services )
for pathIndex , path := range ingress . Paths {
if _ , ok := serviceMap [ path . ServiceName ] ; ok {
ingress . Paths [ pathIndex ] . HasService = true
}
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
return ingress , nil
}
// CombineIngressesWithServices combines a list of ingresses with a list of services that are being used by the ingresses.
// this is required to display the services that are being used by the ingresses in the UI list view.
func ( kcl * KubeClient ) CombineIngressesWithServices ( ingresses [ ] models . K8sIngressInfo ) ( [ ] models . K8sIngressInfo , error ) {
services , err := kcl . GetServices ( "" )
if err != nil {
if k8serrors . IsUnauthorized ( err ) {
return nil , fmt . Errorf ( "an error occurred during the CombineIngressesWithServices operation, unauthorized access to the Kubernetes API. Error: %w" , err )
}
return nil , fmt . Errorf ( "an error occurred during the CombineIngressesWithServices operation, unable to retrieve services from the Kubernetes for a cluster level user. Error: %w" , err )
2022-09-21 04:49:42 +00:00
}
2024-10-01 01:15:51 +00:00
serviceMap := kcl . buildServicesMap ( services )
for ingressIndex , ingress := range ingresses {
for pathIndex , path := range ingress . Paths {
if _ , ok := serviceMap [ path . ServiceName ] ; ok {
( ingresses ) [ ingressIndex ] . Paths [ pathIndex ] . HasService = true
}
2023-06-13 00:38:00 +00:00
}
}
2024-10-01 01:15:51 +00:00
return ingresses , nil
2022-09-21 04:49:42 +00:00
}