2021-02-26 15:50:33 +00:00
import _ from 'lodash-es' ;
2020-07-05 23:21:03 +00:00
import angular from 'angular' ;
2020-08-20 00:51:14 +00:00
import { KubernetesStorageClass , KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models' ;
2021-02-26 15:50:33 +00:00
import { KubernetesFormValidationReferences } from 'Kubernetes/models/application/formValues' ;
2020-08-20 00:51:14 +00:00
import { KubernetesIngressClass } from 'Kubernetes/ingress/models' ;
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper' ;
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants' ;
2021-08-26 14:00:59 +00:00
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper' ;
2021-12-14 19:14:53 +00:00
import { FeatureId } from '@/portainer/feature-flags/enums' ;
2020-07-05 23:21:03 +00:00
class KubernetesConfigureController {
2020-08-20 00:51:14 +00:00
/* #region CONSTRUCTOR */
2021-03-22 19:58:11 +00:00
2020-07-05 23:21:03 +00:00
/* @ngInject */
2021-02-18 01:52:59 +00:00
constructor (
$async ,
$state ,
2021-12-14 19:14:53 +00:00
$scope ,
2021-02-18 01:52:59 +00:00
Notifications ,
KubernetesStorageService ,
EndpointService ,
EndpointProvider ,
ModalService ,
KubernetesResourcePoolService ,
2021-04-29 01:10:14 +00:00
KubernetesIngressService ,
KubernetesMetricsService
2021-02-18 01:52:59 +00:00
) {
2020-07-05 23:21:03 +00:00
this . $async = $async ;
this . $state = $state ;
2021-12-14 19:14:53 +00:00
this . $scope = $scope ;
2020-07-05 23:21:03 +00:00
this . Notifications = Notifications ;
this . KubernetesStorageService = KubernetesStorageService ;
this . EndpointService = EndpointService ;
this . EndpointProvider = EndpointProvider ;
2020-08-20 00:51:14 +00:00
this . ModalService = ModalService ;
2021-02-18 01:52:59 +00:00
this . KubernetesResourcePoolService = KubernetesResourcePoolService ;
this . KubernetesIngressService = KubernetesIngressService ;
2021-04-29 01:10:14 +00:00
this . KubernetesMetricsService = KubernetesMetricsService ;
2020-08-20 00:51:14 +00:00
this . IngressClassTypes = KubernetesIngressClassTypes ;
2020-07-05 23:21:03 +00:00
this . onInit = this . onInit . bind ( this ) ;
this . configureAsync = this . configureAsync . bind ( this ) ;
2021-12-14 19:14:53 +00:00
this . limitedFeature = FeatureId . K8S _SETUP _DEFAULT ;
this . limitedFeatureAutoWindow = FeatureId . HIDE _AUTO _UPDATE _WINDOW ;
this . onToggleAutoUpdate = this . onToggleAutoUpdate . bind ( this ) ;
this . onChangeEnableResourceOverCommit = this . onChangeEnableResourceOverCommit . bind ( this ) ;
2020-07-05 23:21:03 +00:00
}
2020-08-20 00:51:14 +00:00
/* #endregion */
2020-07-05 23:21:03 +00:00
2020-08-20 00:51:14 +00:00
/* #region STORAGE CLASSES UI MANAGEMENT */
2020-07-05 23:21:03 +00:00
storageClassAvailable ( ) {
return this . StorageClasses && this . StorageClasses . length > 0 ;
}
hasValidStorageConfiguration ( ) {
let valid = true ;
_ . forEach ( this . StorageClasses , ( item ) => {
if ( item . selected && item . AccessModes . length === 0 ) {
valid = false ;
}
} ) ;
return valid ;
}
2020-08-20 00:51:14 +00:00
/* #endregion */
/* #region INGRESS CLASSES UI MANAGEMENT */
addIngressClass ( ) {
this . formValues . IngressClasses . push ( new KubernetesIngressClass ( ) ) ;
this . onChangeIngressClass ( ) ;
}
restoreIngressClass ( index ) {
this . formValues . IngressClasses [ index ] . NeedsDeletion = false ;
this . onChangeIngressClass ( ) ;
}
removeIngressClass ( index ) {
if ( ! this . formValues . IngressClasses [ index ] . IsNew ) {
this . formValues . IngressClasses [ index ] . NeedsDeletion = true ;
} else {
this . formValues . IngressClasses . splice ( index , 1 ) ;
}
this . onChangeIngressClass ( ) ;
}
onChangeIngressClass ( ) {
const state = this . state . duplicates . ingressClasses ;
const source = _ . map ( this . formValues . IngressClasses , ( ic ) => ( ic . NeedsDeletion ? undefined : ic . Name ) ) ;
const duplicates = KubernetesFormValidationHelper . getDuplicates ( source ) ;
state . refs = duplicates ;
2021-02-26 15:50:33 +00:00
state . hasRefs = Object . keys ( duplicates ) . length > 0 ;
2020-08-20 00:51:14 +00:00
}
onChangeIngressClassName ( index ) {
const fv = this . formValues . IngressClasses [ index ] ;
if ( _ . includes ( fv . Name , KubernetesIngressClassTypes . NGINX ) ) {
fv . Type = KubernetesIngressClassTypes . NGINX ;
} else if ( _ . includes ( fv . Name , KubernetesIngressClassTypes . TRAEFIK ) ) {
fv . Type = KubernetesIngressClassTypes . TRAEFIK ;
}
this . onChangeIngressClass ( ) ;
}
2020-08-20 09:24:12 +00:00
hasTraefikIngress ( ) {
return _ . find ( this . formValues . IngressClasses , { Type : this . IngressClassTypes . TRAEFIK } ) ;
}
2020-08-20 00:51:14 +00:00
/* #endregion */
2021-12-14 19:14:53 +00:00
onChangeEnableResourceOverCommit ( enabled ) {
this . $scope . $evalAsync ( ( ) => {
this . formValues . EnableResourceOverCommit = enabled ;
if ( enabled ) {
this . formValues . ResourceOverCommitPercentage = 20 ;
}
} ) ;
}
2020-08-20 00:51:14 +00:00
/* #region CONFIGURE */
assignFormValuesToEndpoint ( endpoint , storageClasses , ingressClasses ) {
endpoint . Kubernetes . Configuration . StorageClasses = storageClasses ;
endpoint . Kubernetes . Configuration . UseLoadBalancer = this . formValues . UseLoadBalancer ;
endpoint . Kubernetes . Configuration . UseServerMetrics = this . formValues . UseServerMetrics ;
endpoint . Kubernetes . Configuration . IngressClasses = ingressClasses ;
2021-07-23 05:10:46 +00:00
endpoint . Kubernetes . Configuration . RestrictDefaultNamespace = this . formValues . RestrictDefaultNamespace ;
2020-08-20 00:51:14 +00:00
}
transformFormValues ( ) {
const storageClasses = _ . map ( this . StorageClasses , ( item ) => {
if ( item . selected ) {
const res = new KubernetesStorageClass ( ) ;
res . Name = item . Name ;
res . AccessModes = _ . map ( item . AccessModes , 'Name' ) ;
res . Provisioner = item . Provisioner ;
res . AllowVolumeExpansion = item . AllowVolumeExpansion ;
return res ;
}
} ) ;
_ . pull ( storageClasses , undefined ) ;
const ingressClasses = _ . without (
_ . map ( this . formValues . IngressClasses , ( ic ) => ( ic . NeedsDeletion ? undefined : ic ) ) ,
undefined
) ;
_ . pull ( ingressClasses , undefined ) ;
return [ storageClasses , ingressClasses ] ;
}
2020-07-05 23:21:03 +00:00
2021-02-18 01:52:59 +00:00
async removeIngressesAcrossNamespaces ( ) {
const ingressesToDel = _ . filter ( this . formValues . IngressClasses , { NeedsDeletion : true } ) ;
2021-06-09 19:54:36 +00:00
if ( ! ingressesToDel . length ) {
return ;
}
const promises = [ ] ;
const oldEndpointID = this . EndpointProvider . endpointID ( ) ;
this . EndpointProvider . setEndpointID ( this . endpoint . Id ) ;
try {
const allResourcePools = await this . KubernetesResourcePoolService . get ( ) ;
const resourcePools = _ . filter (
allResourcePools ,
2021-08-26 14:00:59 +00:00
( resourcePool ) => ! KubernetesNamespaceHelper . isSystemNamespace ( resourcePool . Namespace . Name ) && ! KubernetesNamespaceHelper . isDefaultNamespace ( resourcePool . Namespace . Name )
2021-06-09 19:54:36 +00:00
) ;
ingressesToDel . forEach ( ( ingress ) => {
resourcePools . forEach ( ( resourcePool ) => {
promises . push ( this . KubernetesIngressService . delete ( resourcePool . Namespace . Name , ingress . Name ) ) ;
} ) ;
2021-02-18 01:52:59 +00:00
} ) ;
2021-06-09 19:54:36 +00:00
} finally {
this . EndpointProvider . setEndpointID ( oldEndpointID ) ;
}
2021-02-18 01:52:59 +00:00
const responses = await Promise . allSettled ( promises ) ;
responses . forEach ( ( respons ) => {
if ( respons . status == 'rejected' && respons . reason . err . status != 404 ) {
throw respons . reason ;
}
} ) ;
}
2021-06-16 21:47:32 +00:00
2021-04-29 01:10:14 +00:00
enableMetricsServer ( ) {
if ( this . formValues . UseServerMetrics ) {
this . state . metrics . userClick = true ;
this . state . metrics . pending = true ;
this . KubernetesMetricsService . capabilities ( this . endpoint . Id )
. then ( ( ) => {
this . state . metrics . isServerRunning = true ;
this . state . metrics . pending = false ;
this . formValues . UseServerMetrics = true ;
} )
. catch ( ( ) => {
this . state . metrics . isServerRunning = false ;
this . state . metrics . pending = false ;
this . formValues . UseServerMetrics = false ;
} ) ;
} else {
this . state . metrics . userClick = false ;
this . formValues . UseServerMetrics = false ;
}
}
2021-02-18 01:52:59 +00:00
2020-07-05 23:21:03 +00:00
async configureAsync ( ) {
try {
this . state . actionInProgress = true ;
2020-08-20 00:51:14 +00:00
const [ storageClasses , ingressClasses ] = this . transformFormValues ( ) ;
2020-07-05 23:21:03 +00:00
2021-02-18 01:52:59 +00:00
await this . removeIngressesAcrossNamespaces ( ) ;
2020-08-20 00:51:14 +00:00
this . assignFormValuesToEndpoint ( this . endpoint , storageClasses , ingressClasses ) ;
2020-07-05 23:21:03 +00:00
await this . EndpointService . updateEndpoint ( this . endpoint . Id , this . endpoint ) ;
2020-08-07 04:40:24 +00:00
2020-08-20 00:51:14 +00:00
const storagePromises = _ . map ( storageClasses , ( storageClass ) => {
2020-08-07 04:40:24 +00:00
const oldStorageClass = _ . find ( this . oldStorageClasses , { Name : storageClass . Name } ) ;
if ( oldStorageClass ) {
2020-08-07 22:43:34 +00:00
return this . KubernetesStorageService . patch ( this . state . endpointId , oldStorageClass , storageClass ) ;
2020-08-07 04:40:24 +00:00
}
} ) ;
await Promise . all ( storagePromises ) ;
2020-07-05 23:21:03 +00:00
const endpoints = this . EndpointProvider . endpoints ( ) ;
const modifiedEndpoint = _ . find ( endpoints , ( item ) => item . Id === this . endpoint . Id ) ;
if ( modifiedEndpoint ) {
2020-08-20 00:51:14 +00:00
this . assignFormValuesToEndpoint ( modifiedEndpoint , storageClasses , ingressClasses ) ;
2020-07-05 23:21:03 +00:00
this . EndpointProvider . setEndpoints ( endpoints ) ;
}
2022-08-10 05:07:35 +00:00
this . Notifications . success ( 'Success' , 'Configuration successfully applied' ) ;
2020-07-05 23:21:03 +00:00
this . $state . go ( 'portainer.home' ) ;
} catch ( err ) {
this . Notifications . error ( 'Failure' , err , 'Unable to apply configuration' ) ;
} finally {
this . state . actionInProgress = false ;
}
}
configure ( ) {
2020-08-20 00:51:14 +00:00
const toDel = _ . filter ( this . formValues . IngressClasses , { NeedsDeletion : true } ) ;
if ( toDel . length ) {
this . ModalService . confirmUpdate (
2021-02-18 01:52:59 +00:00
` Removing ingress controllers may cause applications to be unaccessible. All ingress configurations from affected applications will be removed.<br/><br/>Do you wish to continue? ` ,
2020-08-20 00:51:14 +00:00
( confirmed ) => {
if ( confirmed ) {
return this . $async ( this . configureAsync ) ;
}
}
) ;
} else {
return this . $async ( this . configureAsync ) ;
}
2020-07-05 23:21:03 +00:00
}
2020-08-20 00:51:14 +00:00
/* #endregion */
2020-07-05 23:21:03 +00:00
2021-09-05 10:03:48 +00:00
restrictDefaultToggledOn ( ) {
return this . formValues . RestrictDefaultNamespace && ! this . oldFormValues . RestrictDefaultNamespace ;
}
2021-12-14 19:14:53 +00:00
onToggleAutoUpdate ( value ) {
return this . $scope . $evalAsync ( ( ) => {
this . state . autoUpdateSettings . Enabled = value ;
} ) ;
}
2020-08-20 00:51:14 +00:00
/* #region ON INIT */
2020-07-05 23:21:03 +00:00
async onInit ( ) {
this . state = {
actionInProgress : false ,
displayConfigureClassPanel : { } ,
viewReady : false ,
2022-07-22 02:14:31 +00:00
endpointId : this . $state . params . endpointId ,
2020-08-20 00:51:14 +00:00
duplicates : {
2021-02-26 15:50:33 +00:00
ingressClasses : new KubernetesFormValidationReferences ( ) ,
2020-08-20 00:51:14 +00:00
} ,
2021-04-29 01:10:14 +00:00
metrics : {
pending : false ,
isServerRunning : false ,
userClick : false ,
} ,
2020-07-05 23:21:03 +00:00
} ;
this . formValues = {
UseLoadBalancer : false ,
2020-08-04 22:08:11 +00:00
UseServerMetrics : false ,
2020-08-20 00:51:14 +00:00
IngressClasses : [ ] ,
2021-07-23 05:10:46 +00:00
RestrictDefaultNamespace : false ,
2020-07-05 23:21:03 +00:00
} ;
try {
2020-08-07 22:43:34 +00:00
[ this . StorageClasses , this . endpoint ] = await Promise . all ( [ this . KubernetesStorageService . get ( this . state . endpointId ) , this . EndpointService . endpoint ( this . state . endpointId ) ] ) ;
2020-07-05 23:21:03 +00:00
_ . forEach ( this . StorageClasses , ( item ) => {
item . availableAccessModes = new KubernetesStorageClassAccessPolicies ( ) ;
const storage = _ . find ( this . endpoint . Kubernetes . Configuration . StorageClasses , ( sc ) => sc . Name === item . Name ) ;
if ( storage ) {
item . selected = true ;
_ . forEach ( storage . AccessModes , ( access ) => {
const mode = _ . find ( item . availableAccessModes , { Name : access } ) ;
if ( mode ) {
mode . selected = true ;
}
} ) ;
}
} ) ;
2020-08-07 04:40:24 +00:00
this . oldStorageClasses = angular . copy ( this . StorageClasses ) ;
2020-07-05 23:21:03 +00:00
this . formValues . UseLoadBalancer = this . endpoint . Kubernetes . Configuration . UseLoadBalancer ;
2020-08-04 22:08:11 +00:00
this . formValues . UseServerMetrics = this . endpoint . Kubernetes . Configuration . UseServerMetrics ;
2021-07-23 05:10:46 +00:00
this . formValues . RestrictDefaultNamespace = this . endpoint . Kubernetes . Configuration . RestrictDefaultNamespace ;
2020-08-20 00:51:14 +00:00
this . formValues . IngressClasses = _ . map ( this . endpoint . Kubernetes . Configuration . IngressClasses , ( ic ) => {
ic . IsNew = false ;
ic . NeedsDeletion = false ;
return ic ;
} ) ;
2021-09-05 10:03:48 +00:00
this . oldFormValues = Object . assign ( { } , this . formValues ) ;
2020-07-05 23:21:03 +00:00
} catch ( err ) {
2021-09-08 08:42:17 +00:00
this . Notifications . error ( 'Failure' , err , 'Unable to retrieve environment configuration' ) ;
2020-07-05 23:21:03 +00:00
} finally {
this . state . viewReady = true ;
}
}
$onInit ( ) {
return this . $async ( this . onInit ) ;
}
2020-08-20 00:51:14 +00:00
/* #endregion */
2020-07-05 23:21:03 +00:00
}
export default KubernetesConfigureController ;
angular . module ( 'portainer.kubernetes' ) . controller ( 'KubernetesConfigureController' , KubernetesConfigureController ) ;