bug(ingress): fix ingress class disallowed to not found issue EE-4311 (#7731)

pull/7742/head
Prabhat Khera 2022-10-05 15:17:53 +13:00 committed by GitHub
parent 66fd039933
commit 83a1ce9d2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 64 deletions

View File

@ -51,41 +51,29 @@ func (handler *Handler) getKubernetesIngressControllers(w http.ResponseWriter, r
controllers := cli.GetIngressControllers() controllers := cli.GetIngressControllers()
existingClasses := endpoint.Kubernetes.Configuration.IngressClasses existingClasses := endpoint.Kubernetes.Configuration.IngressClasses
var updatedClasses []portainer.KubernetesIngressClassConfig
for i := range controllers { for i := range controllers {
controllers[i].Availability = true controllers[i].Availability = true
controllers[i].New = true controllers[i].New = true
// Check if the controller is blocked globally. var updatedClass portainer.KubernetesIngressClassConfig
for _, a := range existingClasses { updatedClass.Name = controllers[i].ClassName
controllers[i].New = false updatedClass.Type = controllers[i].Type
if controllers[i].ClassName != a.Name {
// Check if the controller is already known.
for _, existingClass := range existingClasses {
if controllers[i].ClassName != existingClass.Name {
continue continue
} }
controllers[i].New = false controllers[i].New = false
controllers[i].Availability = !existingClass.GloballyBlocked
// Skip over non-global blocks. updatedClass.GloballyBlocked = existingClass.GloballyBlocked
if len(a.BlockedNamespaces) > 0 { updatedClass.BlockedNamespaces = existingClass.BlockedNamespaces
continue
}
if controllers[i].ClassName == a.Name {
controllers[i].Availability = !a.GloballyBlocked
}
} }
updatedClasses = append(updatedClasses, updatedClass)
} }
// Update the database to match the list of found + modified controllers. endpoint.Kubernetes.Configuration.IngressClasses = updatedClasses
// 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
err = handler.DataStore.Endpoint().UpdateEndpoint( err = handler.DataStore.Endpoint().UpdateEndpoint(
portainer.EndpointID(endpointID), portainer.EndpointID(endpointID),
endpoint, endpoint,
@ -143,7 +131,9 @@ func (handler *Handler) getKubernetesIngressControllersByNamespace(w http.Respon
cli := handler.KubernetesClient cli := handler.KubernetesClient
currentControllers := cli.GetIngressControllers() currentControllers := cli.GetIngressControllers()
existingClasses := endpoint.Kubernetes.Configuration.IngressClasses kubernetesConfig := endpoint.Kubernetes.Configuration
existingClasses := kubernetesConfig.IngressClasses
ingressAvailabilityPerNamespace := kubernetesConfig.IngressAvailabilityPerNamespace
var updatedClasses []portainer.KubernetesIngressClassConfig var updatedClasses []portainer.KubernetesIngressClassConfig
var controllers models.K8sIngressControllers var controllers models.K8sIngressControllers
for i := range currentControllers { for i := range currentControllers {
@ -167,10 +157,12 @@ func (handler *Handler) getKubernetesIngressControllersByNamespace(w http.Respon
globallyblocked = existingClass.GloballyBlocked globallyblocked = existingClass.GloballyBlocked
// Check if the current namespace is blocked. // Check if the current namespace is blocked if ingressAvailabilityPerNamespace is set to true
for _, ns := range existingClass.BlockedNamespaces { if ingressAvailabilityPerNamespace {
if namespace == ns { for _, ns := range existingClass.BlockedNamespaces {
currentControllers[i].Availability = false 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 { func (handler *Handler) updateKubernetesIngressControllers(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil { if err != nil {
return httperror.BadRequest( return httperror.BadRequest(
@ -237,38 +230,38 @@ func (handler *Handler) updateKubernetesIngressControllers(w http.ResponseWriter
existingClasses := endpoint.Kubernetes.Configuration.IngressClasses existingClasses := endpoint.Kubernetes.Configuration.IngressClasses
controllers := cli.GetIngressControllers() controllers := cli.GetIngressControllers()
var updatedClasses []portainer.KubernetesIngressClassConfig
for i := range controllers { for i := range controllers {
// Set existing class data. So that we don't accidentally overwrite it controllers[i].Availability = true
// with blank data that isn't in the payload. controllers[i].New = true
for ii := range existingClasses {
if controllers[i].ClassName == existingClasses[ii].Name { var updatedClass portainer.KubernetesIngressClassConfig
controllers[i].Availability = !existingClasses[ii].GloballyBlocked 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 _, p := range payload {
for i := range controllers { for i := range controllers {
// Now set new payload data // Now set new payload data
if p.ClassName == controllers[i].ClassName { if updatedClasses[i].Name == p.ClassName {
controllers[i].Availability = p.Availability updatedClasses[i].GloballyBlocked = !p.Availability
} }
} }
} }
// Update the database to match the list of found + modified controllers. endpoint.Kubernetes.Configuration.IngressClasses = updatedClasses
// 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
err = handler.DataStore.Endpoint().UpdateEndpoint( err = handler.DataStore.Endpoint().UpdateEndpoint(
portainer.EndpointID(endpointID), portainer.EndpointID(endpointID),
endpoint, endpoint,
@ -327,7 +320,6 @@ PayloadLoop:
for _, p := range payload { for _, p := range payload {
for _, existingClass := range existingClasses { for _, existingClass := range existingClasses {
if p.ClassName != existingClass.Name { if p.ClassName != existingClass.Name {
updatedClasses = append(updatedClasses, existingClass)
continue continue
} }
var updatedClass portainer.KubernetesIngressClassConfig var updatedClass portainer.KubernetesIngressClassConfig
@ -345,7 +337,7 @@ PayloadLoop:
} }
} }
updatedClasses = append(updatedClasses, existingClass) updatedClasses = append(updatedClasses, updatedClass)
continue PayloadLoop continue PayloadLoop
} }
@ -356,7 +348,7 @@ PayloadLoop:
updatedClass.BlockedNamespaces = existingClass.BlockedNamespaces updatedClass.BlockedNamespaces = existingClass.BlockedNamespaces
for _, ns := range updatedClass.BlockedNamespaces { for _, ns := range updatedClass.BlockedNamespaces {
if namespace == ns { if namespace == ns {
updatedClasses = append(updatedClasses, existingClass) updatedClasses = append(updatedClasses, updatedClass)
continue PayloadLoop 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 endpoint.Kubernetes.Configuration.IngressClasses = updatedClasses
err = handler.DataStore.Endpoint().UpdateEndpoint( err = handler.DataStore.Endpoint().UpdateEndpoint(
portainer.EndpointID(endpointID), portainer.EndpointID(endpointID),

View File

@ -14,7 +14,7 @@ import { PageHeader } from '@@/PageHeader';
import { Option } from '@@/form-components/Input/Select'; import { Option } from '@@/form-components/Input/Select';
import { Button } from '@@/buttons'; import { Button } from '@@/buttons';
import { Ingress } from '../types'; import { Ingress, IngressController } from '../types';
import { import {
useCreateIngress, useCreateIngress,
useIngresses, useIngresses,
@ -66,7 +66,8 @@ export function CreateIngressView() {
(servicesResults.isLoading && (servicesResults.isLoading &&
configResults.isLoading && configResults.isLoading &&
namespacesResults.isLoading && namespacesResults.isLoading &&
ingressesResults.isLoading) || ingressesResults.isLoading &&
ingressControllersResults.isLoading) ||
(isEdit && !ingressRule.IngressName); (isEdit && !ingressRule.IngressName);
const [ingressNames, ingresses, ruleCounterByNamespace, hostWithTLS] = 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({ ingressClassOptions.push({
label: !ingressRule.IngressType label: !ingressRule.IngressType
? `${ingressRule.IngressClassName} - NOT FOUND` ? `${ingressRule.IngressClassName} - NOT FOUND`
@ -189,7 +195,12 @@ export function CreateIngressView() {
]; ];
useEffect(() => { 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 // if it is an edit screen, prepare the rule from the ingress
const ing = ingressesResults.data?.find( const ing = ingressesResults.data?.find(
(ing) => ing.Name === params.name && ing.Namespace === params.namespace (ing) => ing.Name === params.name && ing.Namespace === params.namespace
@ -199,7 +210,7 @@ export function CreateIngressView() {
(c) => c.ClassName === ing.ClassName (c) => c.ClassName === ing.ClassName
)?.Type; )?.Type;
const r = prepareRuleFromIngress(ing); const r = prepareRuleFromIngress(ing);
r.IngressType = type; r.IngressType = type || r.IngressType;
setIngressRule(r); setIngressRule(r);
} }
} }
@ -217,9 +228,10 @@ export function CreateIngressView() {
ingressRule, ingressRule,
ingressNames || [], ingressNames || [],
servicesOptions || [], servicesOptions || [],
!!existingIngressClass existingIngressClass
); );
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
ingressRule, ingressRule,
namespace, namespace,
@ -289,7 +301,7 @@ export function CreateIngressView() {
ingressRule: Rule, ingressRule: Rule,
ingressNames: string[], ingressNames: string[],
serviceOptions: Option<string>[], serviceOptions: Option<string>[],
existingIngressClass: boolean existingIngressClass?: IngressController
) { ) {
const errors: Record<string, ReactNode> = {}; const errors: Record<string, ReactNode> = {};
const rule = { ...ingressRule }; 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.'; '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) { if (!rule.IngressType) {
errors.className = errors.className =
'Currently set to an ingress class that cannot be found in the cluster - you must select a valid class.'; 'Currently set to an ingress class that cannot be found in the cluster - you must select a valid class.';

View File

@ -102,8 +102,9 @@ export function IngressForm({
} }
const hasNoHostRule = rule.Hosts?.some((host) => host.NoHost); const hasNoHostRule = rule.Hosts?.some((host) => host.NoHost);
const placeholderAnnotation = const placeholderAnnotation =
PlaceholderAnnotations[rule.IngressType || 'other']; PlaceholderAnnotations[rule.IngressType || 'other'] ||
const pathTypes = PathTypes[rule.IngressType || 'other']; PlaceholderAnnotations.other;
const pathTypes = PathTypes[rule.IngressType || 'other'] || PathTypes.other;
return ( return (
<Widget> <Widget>
@ -169,7 +170,7 @@ export function IngressForm({
</div> </div>
</div> </div>
<div className="form-group" key={rule.IngressClassName}> <div className="form-group" key={ingressClassOptions.toString()}>
<label <label
className="control-label text-muted col-sm-3 col-lg-2 required" className="control-label text-muted col-sm-3 col-lg-2 required"
htmlFor="ingress_class" htmlFor="ingress_class"