diff --git a/api/http/handler/kubernetes/ingresses.go b/api/http/handler/kubernetes/ingresses.go index f12492c13..0dfb8fadb 100644 --- a/api/http/handler/kubernetes/ingresses.go +++ b/api/http/handler/kubernetes/ingresses.go @@ -51,41 +51,29 @@ func (handler *Handler) getKubernetesIngressControllers(w http.ResponseWriter, r controllers := cli.GetIngressControllers() existingClasses := endpoint.Kubernetes.Configuration.IngressClasses + var updatedClasses []portainer.KubernetesIngressClassConfig for i := range controllers { controllers[i].Availability = true controllers[i].New = true - // Check if the controller is blocked globally. - for _, a := range existingClasses { - controllers[i].New = false - if controllers[i].ClassName != a.Name { - continue - } - controllers[i].New = false + var updatedClass portainer.KubernetesIngressClassConfig + updatedClass.Name = controllers[i].ClassName + updatedClass.Type = controllers[i].Type - // Skip over non-global blocks. - if len(a.BlockedNamespaces) > 0 { + // Check if the controller is already known. + for _, existingClass := range existingClasses { + if controllers[i].ClassName != existingClass.Name { continue } - - if controllers[i].ClassName == a.Name { - controllers[i].Availability = !a.GloballyBlocked - } + controllers[i].New = false + controllers[i].Availability = !existingClass.GloballyBlocked + updatedClass.GloballyBlocked = existingClass.GloballyBlocked + updatedClass.BlockedNamespaces = existingClass.BlockedNamespaces } + updatedClasses = append(updatedClasses, updatedClass) } - // Update the database to match the list of found + modified controllers. - // This includes pruning out controllers which no longer exist. - var newClasses []portainer.KubernetesIngressClassConfig - for _, controller := range controllers { - var class portainer.KubernetesIngressClassConfig - class.Name = controller.ClassName - class.Type = controller.Type - class.GloballyBlocked = !controller.Availability - class.BlockedNamespaces = []string{} - newClasses = append(newClasses, class) - } - endpoint.Kubernetes.Configuration.IngressClasses = newClasses + endpoint.Kubernetes.Configuration.IngressClasses = updatedClasses err = handler.DataStore.Endpoint().UpdateEndpoint( portainer.EndpointID(endpointID), endpoint, @@ -143,7 +131,9 @@ func (handler *Handler) getKubernetesIngressControllersByNamespace(w http.Respon cli := handler.KubernetesClient currentControllers := cli.GetIngressControllers() - existingClasses := endpoint.Kubernetes.Configuration.IngressClasses + kubernetesConfig := endpoint.Kubernetes.Configuration + existingClasses := kubernetesConfig.IngressClasses + ingressAvailabilityPerNamespace := kubernetesConfig.IngressAvailabilityPerNamespace var updatedClasses []portainer.KubernetesIngressClassConfig var controllers models.K8sIngressControllers for i := range currentControllers { @@ -167,10 +157,12 @@ func (handler *Handler) getKubernetesIngressControllersByNamespace(w http.Respon globallyblocked = existingClass.GloballyBlocked - // Check if the current namespace is blocked. - for _, ns := range existingClass.BlockedNamespaces { - if namespace == ns { - currentControllers[i].Availability = false + // Check if the current namespace is blocked if ingressAvailabilityPerNamespace is set to true + if ingressAvailabilityPerNamespace { + for _, ns := range existingClass.BlockedNamespaces { + if namespace == ns { + currentControllers[i].Availability = false + } } } } @@ -197,6 +189,7 @@ func (handler *Handler) getKubernetesIngressControllersByNamespace(w http.Respon } func (handler *Handler) updateKubernetesIngressControllers(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") if err != nil { return httperror.BadRequest( @@ -237,38 +230,38 @@ func (handler *Handler) updateKubernetesIngressControllers(w http.ResponseWriter existingClasses := endpoint.Kubernetes.Configuration.IngressClasses controllers := cli.GetIngressControllers() + var updatedClasses []portainer.KubernetesIngressClassConfig for i := range controllers { - // Set existing class data. So that we don't accidentally overwrite it - // with blank data that isn't in the payload. - for ii := range existingClasses { - if controllers[i].ClassName == existingClasses[ii].Name { - controllers[i].Availability = !existingClasses[ii].GloballyBlocked + controllers[i].Availability = true + controllers[i].New = true + + var updatedClass portainer.KubernetesIngressClassConfig + updatedClass.Name = controllers[i].ClassName + updatedClass.Type = controllers[i].Type + + // Check if the controller is already known. + for _, existingClass := range existingClasses { + if controllers[i].ClassName != existingClass.Name { + continue } + controllers[i].New = false + controllers[i].Availability = !existingClass.GloballyBlocked + updatedClass.GloballyBlocked = existingClass.GloballyBlocked + updatedClass.BlockedNamespaces = existingClass.BlockedNamespaces } + updatedClasses = append(updatedClasses, updatedClass) } for _, p := range payload { for i := range controllers { // Now set new payload data - if p.ClassName == controllers[i].ClassName { - controllers[i].Availability = p.Availability + if updatedClasses[i].Name == p.ClassName { + updatedClasses[i].GloballyBlocked = !p.Availability } } } - // Update the database to match the list of found + modified controllers. - // This includes pruning out controllers which no longer exist. - var newClasses []portainer.KubernetesIngressClassConfig - for _, controller := range controllers { - var class portainer.KubernetesIngressClassConfig - class.Name = controller.ClassName - class.Type = controller.Type - class.GloballyBlocked = !controller.Availability - class.BlockedNamespaces = []string{} - newClasses = append(newClasses, class) - } - - endpoint.Kubernetes.Configuration.IngressClasses = newClasses + endpoint.Kubernetes.Configuration.IngressClasses = updatedClasses err = handler.DataStore.Endpoint().UpdateEndpoint( portainer.EndpointID(endpointID), endpoint, @@ -327,7 +320,6 @@ PayloadLoop: for _, p := range payload { for _, existingClass := range existingClasses { if p.ClassName != existingClass.Name { - updatedClasses = append(updatedClasses, existingClass) continue } var updatedClass portainer.KubernetesIngressClassConfig @@ -345,7 +337,7 @@ PayloadLoop: } } - updatedClasses = append(updatedClasses, existingClass) + updatedClasses = append(updatedClasses, updatedClass) continue PayloadLoop } @@ -356,7 +348,7 @@ PayloadLoop: updatedClass.BlockedNamespaces = existingClass.BlockedNamespaces for _, ns := range updatedClass.BlockedNamespaces { if namespace == ns { - updatedClasses = append(updatedClasses, existingClass) + updatedClasses = append(updatedClasses, updatedClass) continue PayloadLoop } } @@ -368,6 +360,24 @@ PayloadLoop: } } + // At this point it's possible we had an existing class which was globally + // blocked and thus not included in the payload. As a result it is not yet + // part of updatedClasses, but we MUST include it or we would remove the + // global block. + for _, existingClass := range existingClasses { + var found bool + + for _, updatedClass := range updatedClasses { + if existingClass.Name == updatedClass.Name { + found = true + } + } + + if !found { + updatedClasses = append(updatedClasses, existingClass) + } + } + endpoint.Kubernetes.Configuration.IngressClasses = updatedClasses err = handler.DataStore.Endpoint().UpdateEndpoint( portainer.EndpointID(endpointID), diff --git a/app/kubernetes/react/views/networks/ingresses/CreateIngressView/CreateIngressView.tsx b/app/kubernetes/react/views/networks/ingresses/CreateIngressView/CreateIngressView.tsx index 7748e58da..1ec1a5932 100644 --- a/app/kubernetes/react/views/networks/ingresses/CreateIngressView/CreateIngressView.tsx +++ b/app/kubernetes/react/views/networks/ingresses/CreateIngressView/CreateIngressView.tsx @@ -14,7 +14,7 @@ import { PageHeader } from '@@/PageHeader'; import { Option } from '@@/form-components/Input/Select'; import { Button } from '@@/buttons'; -import { Ingress } from '../types'; +import { Ingress, IngressController } from '../types'; import { useCreateIngress, useIngresses, @@ -66,7 +66,8 @@ export function CreateIngressView() { (servicesResults.isLoading && configResults.isLoading && namespacesResults.isLoading && - ingressesResults.isLoading) || + ingressesResults.isLoading && + ingressControllersResults.isLoading) || (isEdit && !ingressRule.IngressName); const [ingressNames, ingresses, ruleCounterByNamespace, hostWithTLS] = @@ -166,7 +167,12 @@ export function CreateIngressView() { })) || []), ]; - if (!existingIngressClass && ingressRule.IngressClassName) { + if ( + (!existingIngressClass || + (existingIngressClass && !existingIngressClass.Availability)) && + ingressRule.IngressClassName && + ingressControllersResults.data + ) { ingressClassOptions.push({ label: !ingressRule.IngressType ? `${ingressRule.IngressClassName} - NOT FOUND` @@ -189,7 +195,12 @@ export function CreateIngressView() { ]; useEffect(() => { - if (!!params.name && ingressesResults.data && !ingressRule.IngressName) { + if ( + !!params.name && + ingressesResults.data && + !ingressRule.IngressName && + ingressControllersResults.data + ) { // if it is an edit screen, prepare the rule from the ingress const ing = ingressesResults.data?.find( (ing) => ing.Name === params.name && ing.Namespace === params.namespace @@ -199,7 +210,7 @@ export function CreateIngressView() { (c) => c.ClassName === ing.ClassName )?.Type; const r = prepareRuleFromIngress(ing); - r.IngressType = type; + r.IngressType = type || r.IngressType; setIngressRule(r); } } @@ -217,9 +228,10 @@ export function CreateIngressView() { ingressRule, ingressNames || [], servicesOptions || [], - !!existingIngressClass + existingIngressClass ); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ ingressRule, namespace, @@ -289,7 +301,7 @@ export function CreateIngressView() { ingressRule: Rule, ingressNames: string[], serviceOptions: Option[], - existingIngressClass: boolean + existingIngressClass?: IngressController ) { const errors: Record = {}; const rule = { ...ingressRule }; @@ -320,7 +332,12 @@ export function CreateIngressView() { 'No ingress class is currently set for this ingress - use of the Portainer UI requires one to be set.'; } - if (isEdit && !existingIngressClass && ingressRule.IngressClassName) { + if ( + isEdit && + (!existingIngressClass || + (existingIngressClass && !existingIngressClass.Availability)) && + ingressRule.IngressClassName + ) { if (!rule.IngressType) { errors.className = 'Currently set to an ingress class that cannot be found in the cluster - you must select a valid class.'; diff --git a/app/kubernetes/react/views/networks/ingresses/CreateIngressView/IngressForm.tsx b/app/kubernetes/react/views/networks/ingresses/CreateIngressView/IngressForm.tsx index ff3661f0f..f14abe94c 100644 --- a/app/kubernetes/react/views/networks/ingresses/CreateIngressView/IngressForm.tsx +++ b/app/kubernetes/react/views/networks/ingresses/CreateIngressView/IngressForm.tsx @@ -102,8 +102,9 @@ export function IngressForm({ } const hasNoHostRule = rule.Hosts?.some((host) => host.NoHost); const placeholderAnnotation = - PlaceholderAnnotations[rule.IngressType || 'other']; - const pathTypes = PathTypes[rule.IngressType || 'other']; + PlaceholderAnnotations[rule.IngressType || 'other'] || + PlaceholderAnnotations.other; + const pathTypes = PathTypes[rule.IngressType || 'other'] || PathTypes.other; return ( @@ -169,7 +170,7 @@ export function IngressForm({ -
+