1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 05:45:22 +02:00

fix(ingress): allow none controller type EE-4420 (#7883)

Co-authored-by: testA113 <alex.harris@portainer.io>
This commit is contained in:
Dakota Walsh 2022-10-25 09:41:30 +13:00 committed by GitHub
parent e48ceb15e9
commit 55211ef00e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 243 additions and 76 deletions

View file

@ -12,9 +12,10 @@ export const componentsModule = angular
.component(
'ingressClassDatatable',
r2a(IngressClassDatatable, [
'onChangeAvailability',
'onChangeControllers',
'description',
'ingressControllers',
'allowNoneIngressClass',
'isLoading',
'noIngressControllerLabel',
'view',

View file

@ -157,7 +157,9 @@ export function CreateIngressView() {
const existingIngressClass = useMemo(
() =>
ingressControllersResults.data?.find(
(i) => i.ClassName === ingressRule.IngressClassName
(i) =>
i.ClassName === ingressRule.IngressClassName ||
(i.Type === 'custom' && ingressRule.IngressClassName === '')
),
[ingressControllersResults.data, ingressRule.IngressClassName]
);
@ -177,10 +179,11 @@ export function CreateIngressView() {
ingressRule.IngressClassName &&
!ingressControllersResults.isLoading
) {
const optionLabel = !ingressRule.IngressType
? `${ingressRule.IngressClassName} - NOT FOUND`
: `${ingressRule.IngressClassName} - DISALLOWED`;
ingressClassOptions.push({
label: !ingressRule.IngressType
? `${ingressRule.IngressClassName} - NOT FOUND`
: `${ingressRule.IngressClassName} - DISALLOWED`,
label: optionLabel,
value: ingressRule.IngressClassName,
});
}
@ -206,6 +209,7 @@ export function CreateIngressView() {
!!params.name &&
ingressesResults.data &&
!ingressRule.IngressName &&
!ingressControllersResults.isLoading &&
!ingressControllersResults.isLoading
) {
// if it is an edit screen, prepare the rule from the ingress
@ -214,9 +218,11 @@ export function CreateIngressView() {
);
if (ing) {
const type = ingressControllersResults.data?.find(
(c) => c.ClassName === ing.ClassName
(c) =>
c.ClassName === ing.ClassName ||
(c.Type === 'custom' && !ing.ClassName)
)?.Type;
const r = prepareRuleFromIngress(ing);
const r = prepareRuleFromIngress(ing, type);
r.IngressType = type || r.IngressType;
setIngressRule(r);
}
@ -636,7 +642,7 @@ export function CreateIngressView() {
setIngressRule(rule);
}
function addNewAnnotation(type?: 'rewrite' | 'regex') {
function addNewAnnotation(type?: 'rewrite' | 'regex' | 'ingressClass') {
const rule = { ...ingressRule };
const annotation: Annotation = {
@ -644,13 +650,21 @@ export function CreateIngressView() {
Value: '',
ID: uuidv4(),
};
if (type === 'rewrite') {
annotation.Key = 'nginx.ingress.kubernetes.io/rewrite-target';
annotation.Value = '/$1';
}
if (type === 'regex') {
annotation.Key = 'nginx.ingress.kubernetes.io/use-regex';
annotation.Value = 'true';
switch (type) {
case 'rewrite':
annotation.Key = 'nginx.ingress.kubernetes.io/rewrite-target';
annotation.Value = '/$1';
break;
case 'regex':
annotation.Key = 'nginx.ingress.kubernetes.io/use-regex';
annotation.Value = 'true';
break;
case 'ingressClass':
annotation.Key = 'kubernetes.io/ingress.class';
annotation.Value = '';
break;
default:
break;
}
rule.Annotations = rule.Annotations || [];
rule.Annotations?.push(annotation);
@ -690,10 +704,13 @@ export function CreateIngressView() {
function handleCreateIngressRules() {
const rule = { ...ingressRule };
const classNameToSend =
rule.IngressClassName === 'none' ? '' : rule.IngressClassName;
const ingress: Ingress = {
Namespace: namespace,
Name: rule.IngressName,
ClassName: rule.IngressClassName,
ClassName: classNameToSend,
Hosts: rule.Hosts.map((host) => host.Host),
Paths: preparePaths(rule.IngressName, rule.Hosts),
TLS: prepareTLS(rule.Hosts),

View file

@ -47,7 +47,7 @@ interface Props {
addNewIngressHost: (noHost?: boolean) => void;
addNewIngressRoute: (hostIndex: number) => void;
addNewAnnotation: (type?: 'rewrite' | 'regex') => void;
addNewAnnotation: (type?: 'rewrite' | 'regex' | 'ingressClass') => void;
handleNamespaceChange: (val: string) => void;
handleHostChange: (hostIndex: number, val: string) => void;
@ -249,9 +249,10 @@ export function IngressForm({
onClick={() => addNewAnnotation('rewrite')}
icon={Plus}
title="When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to."
data-cy="add-rewrite-annotation"
>
{' '}
add rewrite annotation
Add rewrite annotation
</Button>
<Button
@ -259,11 +260,23 @@ export function IngressForm({
onClick={() => addNewAnnotation('regex')}
icon={Plus}
title="When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to."
data-cy="add-regex-annotation"
>
add regular expression annotation
Add regular expression annotation
</Button>
</>
)}
{rule.IngressType === 'custom' && (
<Button
className="btn btn-sm btn-light mb-2 ml-2"
onClick={() => addNewAnnotation('ingressClass')}
icon={Plus}
data-cy="add-ingress-class-annotation"
>
Add kubernetes.io/ingress.class annotation
</Button>
)}
</div>
<div className="col-sm-12 px-0 text-muted">Rules</div>

View file

@ -1,6 +1,7 @@
import { v4 as uuidv4 } from 'uuid';
import { Annotation } from '@/kubernetes/react/views/networks/ingresses/components/annotations/types';
import { SupportedIngControllerTypes } from '@/react/kubernetes/cluster/ingressClass/types';
import { TLS, Ingress } from '../types';
@ -62,7 +63,7 @@ export function prepareRuleHostsFromIngress(ing: Ingress) {
h.Host = host;
h.Secret = getSecretByHost(host, ing.TLS);
h.Paths = [];
ing.Paths.forEach((path) => {
ing.Paths?.forEach((path) => {
if (path.Host === host) {
h.Paths.push({
Route: path.Path,
@ -99,12 +100,15 @@ export function getAnnotationsForEdit(
return result;
}
export function prepareRuleFromIngress(ing: Ingress): Rule {
export function prepareRuleFromIngress(
ing: Ingress,
type?: SupportedIngControllerTypes
): Rule {
return {
Key: uuidv4(),
IngressName: ing.Name,
Namespace: ing.Namespace,
IngressClassName: ing.ClassName,
IngressClassName: type === 'custom' ? 'none' : ing.ClassName,
Hosts: prepareRuleHostsFromIngress(ing) || [],
Annotations: ing.Annotations ? getAnnotationsForEdit(ing.Annotations) : [],
IngressType: ing.Type,

View file

@ -96,8 +96,11 @@ export function useIngresses(
const serviceNamesInNamespace = servicesInNamespace?.map(
(service) => service.Name
);
ing.Paths.forEach((path, pIndex) => {
if (!serviceNamesInNamespace?.includes(path.ServiceName)) {
ing.Paths?.forEach((path, pIndex) => {
if (
!serviceNamesInNamespace?.includes(path.ServiceName) &&
filteredIngresses[iIndex].Paths
) {
filteredIngresses[iIndex].Paths[pIndex].HasService = false;
} else {
filteredIngresses[iIndex].Paths[pIndex].HasService = true;
@ -186,6 +189,7 @@ export function useIngressControllers(
},
{
enabled: !!namespace,
cacheTime: 0,
...withError('Unable to get ingress controllers'),
}
);

View file

@ -2,6 +2,7 @@ import {
PaginationTableSettings,
SortableTableSettings,
} from '@/react/components/datatables/types';
import { SupportedIngControllerTypes } from '@/react/kubernetes/cluster/ingressClass/types';
export interface TableSettings
extends SortableTableSettings,
@ -42,6 +43,6 @@ export interface IngressController {
Name: string;
ClassName: string;
Availability: string;
Type: string;
Type: SupportedIngControllerTypes;
New: boolean;
}

View file

@ -42,7 +42,8 @@
</div>
<ingress-class-datatable
on-change-availability="(ctrl.onChangeAvailability)"
on-change-controllers="(ctrl.onChangeControllers)"
allow-none-ingress-class="ctrl.formValues.AllowNoneIngressClass"
ingress-controllers="ctrl.originalIngressControllers"
is-loading="ctrl.isIngressControllersLoading"
description="'Enabling ingress controllers in your cluster allows them to be available in the Portainer UI for users to publish applications over HTTP/HTTPS. A controller must have a class name for it to be included here.'"
@ -50,18 +51,46 @@
view="'cluster'"
></ingress-class-datatable>
<div class="form-group">
<div class="col-sm-12">
<por-switch-field
checked="ctrl.formValues.IngressAvailabilityPerNamespace"
name="'ingressAvailabilityPerNamespace'"
label="'Configure ingress controller availability per namespace'"
tooltip="'This allows an administrator to configure, in each namespace, which ingress controllers will be available for users to select when setting up ingresses for applications.'"
on-change="(ctrl.onToggleIngressAvailabilityPerNamespace)"
label-class="'col-sm-5 col-lg-4 px-0 !m-0'"
switch-class="'col-sm-8'"
>
</por-switch-field>
<label htmlFor="foldingButtonIngControllerSettings" class="col-sm-12 form-section-title cursor-pointer flex items-center">
<button
id="foldingButtonIngControllerSettings"
type="button"
class="border-0 mx-2 bg-transparent inline-flex justify-center items-center w-2 !ml-0"
ng-click="ctrl.toggleAdvancedIngSettings()"
>
<pr-icon ng-if="!ctrl.state.isIngToggleSectionExpanded" feather="true" icon="'chevron-right'"></pr-icon>
<pr-icon ng-if="ctrl.state.isIngToggleSectionExpanded" feather="true" icon="'chevron-down'"></pr-icon>
</button>
More settings
</label>
<div ng-if="ctrl.state.isIngToggleSectionExpanded" class="ml-4">
<div class="form-group">
<div class="col-sm-12">
<por-switch-field
checked="ctrl.formValues.AllowNoneIngressClass"
name="'allowNoIngressClass'"
label="'Allow ingress class to be set to &quot;none&quot;'"
tooltip="'This allows users setting up ingresses to select &quot;none&quot; as the ingress class.'"
on-change="(ctrl.onToggleAllowNoneIngressClass)"
label-class="'col-sm-5 col-lg-4 px-0 !m-0'"
switch-class="'col-sm-8'"
>
</por-switch-field>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<por-switch-field
checked="ctrl.formValues.IngressAvailabilityPerNamespace"
name="'ingressAvailabilityPerNamespace'"
label="'Configure ingress controller availability per namespace'"
tooltip="'This allows an administrator to configure, in each namespace, which ingress controllers will be available for users to select when setting up ingresses for applications.'"
on-change="(ctrl.onToggleIngressAvailabilityPerNamespace)"
label-class="'col-sm-5 col-lg-4 px-0 !m-0'"
switch-class="'col-sm-8'"
>
</por-switch-field>
</div>
</div>
</div>

View file

@ -47,9 +47,10 @@ class KubernetesConfigureController {
this.limitedFeature = FeatureId.K8S_SETUP_DEFAULT;
this.limitedFeatureAutoWindow = FeatureId.HIDE_AUTO_UPDATE_WINDOW;
this.onToggleAutoUpdate = this.onToggleAutoUpdate.bind(this);
this.onChangeAvailability = this.onChangeAvailability.bind(this);
this.onChangeControllers = this.onChangeControllers.bind(this);
this.onChangeEnableResourceOverCommit = this.onChangeEnableResourceOverCommit.bind(this);
this.onToggleIngressAvailabilityPerNamespace = this.onToggleIngressAvailabilityPerNamespace.bind(this);
this.onToggleAllowNoneIngressClass = this.onToggleAllowNoneIngressClass.bind(this);
this.onChangeStorageClassAccessMode = this.onChangeStorageClassAccessMode.bind(this);
}
/* #endregion */
@ -71,7 +72,7 @@ class KubernetesConfigureController {
/* #endregion */
/* #region INGRESS CLASSES UI MANAGEMENT */
onChangeAvailability(controllerClassMap) {
onChangeControllers(controllerClassMap) {
this.ingressControllers = controllerClassMap;
}
@ -79,6 +80,18 @@ class KubernetesConfigureController {
return _.find(this.formValues.IngressClasses, { Type: this.IngressClassTypes.TRAEFIK });
}
toggleAdvancedIngSettings() {
this.$scope.$evalAsync(() => {
this.state.isIngToggleSectionExpanded = !this.state.isIngToggleSectionExpanded;
});
}
onToggleAllowNoneIngressClass() {
this.$scope.$evalAsync(() => {
this.formValues.AllowNoneIngressClass = !this.formValues.AllowNoneIngressClass;
});
}
onToggleIngressAvailabilityPerNamespace() {
this.$scope.$evalAsync(() => {
this.formValues.IngressAvailabilityPerNamespace = !this.formValues.IngressAvailabilityPerNamespace;
@ -109,6 +122,7 @@ class KubernetesConfigureController {
endpoint.Kubernetes.Configuration.IngressClasses = ingressClasses;
endpoint.Kubernetes.Configuration.RestrictDefaultNamespace = this.formValues.RestrictDefaultNamespace;
endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace = this.formValues.IngressAvailabilityPerNamespace;
endpoint.Kubernetes.Configuration.AllowNoneIngressClass = this.formValues.AllowNoneIngressClass;
endpoint.ChangeWindow = this.state.autoUpdateSettings;
}
@ -256,6 +270,7 @@ class KubernetesConfigureController {
actionInProgress: false,
displayConfigureClassPanel: {},
viewReady: false,
isIngToggleSectionExpanded: false,
endpointId: this.$state.params.endpointId,
duplicates: {
ingressClasses: new KubernetesFormValidationReferences(),
@ -315,6 +330,7 @@ class KubernetesConfigureController {
return ic;
});
this.formValues.IngressAvailabilityPerNamespace = this.endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace;
this.formValues.AllowNoneIngressClass = this.endpoint.Kubernetes.Configuration.AllowNoneIngressClass;
this.oldFormValues = Object.assign({}, this.formValues);
} catch (err) {

View file

@ -185,7 +185,7 @@
<div class="col-sm-12 form-section-title"> Networking </div>
<ingress-class-datatable
ng-if="$ctrl.state.ingressAvailabilityPerNamespace"
on-change-availability="($ctrl.onChangeIngressControllerAvailability)"
on-change-controllers="($ctrl.onChangeIngressControllerAvailability)"
ingress-controllers="$ctrl.ingressControllers"
description="'Enable the ingress controllers that users can select when publishing applications in this namespace.'"
no-ingress-controller-label="'No ingress controllers found in the cluster. Go to the cluster setup view to configure and allow the use of ingress controllers in the cluster.'"

View file

@ -161,7 +161,7 @@
<div class="col-sm-12 form-section-title"> Networking </div>
<ingress-class-datatable
ng-if="ctrl.state.ingressAvailabilityPerNamespace"
on-change-availability="(ctrl.onChangeIngressControllerAvailability)"
on-change-controllers="(ctrl.onChangeIngressControllerAvailability)"
ingress-controllers="ctrl.ingressControllers"
description="'Enable the ingress controllers that users can select when publishing applications in this namespace.'"
no-ingress-controller-label="'No ingress controllers found in the cluster. Go to the cluster setup view to configure and allow the use of ingress controllers in the cluster.'"