2020-07-05 23:21:03 +00:00
import angular from 'angular' ;
2021-02-26 15:50:33 +00:00
import _ from 'lodash-es' ;
2020-07-29 22:25:59 +00:00
import * as JsonPatch from 'fast-json-patch' ;
2022-12-12 01:30:05 +00:00
import { FeatureId } from '@/react/portainer/feature-flags/enums' ;
2022-01-16 19:37:46 +00:00
2021-09-07 00:37:26 +00:00
import {
KubernetesApplicationDataAccessPolicies ,
KubernetesApplicationDeploymentTypes ,
KubernetesApplicationTypes ,
KubernetesDeploymentTypes ,
} from 'Kubernetes/models/application/models' ;
2020-07-05 23:21:03 +00:00
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper' ;
import KubernetesApplicationHelper from 'Kubernetes/helpers/application' ;
2020-07-14 20:45:19 +00:00
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models' ;
2020-07-29 22:25:59 +00:00
import { KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models' ;
2020-08-13 23:27:10 +00:00
import { KubernetesPodContainerTypes } from 'Kubernetes/pod/models/index' ;
2021-08-26 14:00:59 +00:00
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper' ;
2020-07-29 22:25:59 +00:00
function computeTolerations ( nodes , application ) {
const pod = application . Pods [ 0 ] ;
_ . forEach ( nodes , ( n ) => {
n . AcceptsApplication = true ;
n . Expanded = false ;
if ( ! pod ) {
return ;
}
n . UnmetTaints = [ ] ;
_ . forEach ( n . Taints , ( t ) => {
2020-08-11 23:42:55 +00:00
const matchKeyMatchValueMatchEffect = _ . find ( pod . Tolerations , { Key : t . Key , Operator : 'Equal' , Value : t . Value , Effect : t . Effect } ) ;
const matchKeyAnyValueMatchEffect = _ . find ( pod . Tolerations , { Key : t . Key , Operator : 'Exists' , Effect : t . Effect } ) ;
const matchKeyMatchValueAnyEffect = _ . find ( pod . Tolerations , { Key : t . Key , Operator : 'Equal' , Value : t . Value , Effect : '' } ) ;
const matchKeyAnyValueAnyEffect = _ . find ( pod . Tolerations , { Key : t . Key , Operator : 'Exists' , Effect : '' } ) ;
2020-07-29 22:25:59 +00:00
const anyKeyAnyValueAnyEffect = _ . find ( pod . Tolerations , { Key : '' , Operator : 'Exists' , Effect : '' } ) ;
if ( ! matchKeyMatchValueMatchEffect && ! matchKeyAnyValueMatchEffect && ! matchKeyMatchValueAnyEffect && ! matchKeyAnyValueAnyEffect && ! anyKeyAnyValueAnyEffect ) {
n . AcceptsApplication = false ;
n . UnmetTaints . push ( t ) ;
} else {
n . AcceptsApplication = true ;
}
} ) ;
} ) ;
return nodes ;
}
// For node requirement format depending on operator value
// see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#nodeselectorrequirement-v1-core
// Some operators require empty "values" field, some only one element in "values" field, etc
function computeAffinities ( nodes , application ) {
2020-08-31 05:21:25 +00:00
if ( ! application . Pods || application . Pods . length === 0 ) {
return nodes ;
}
2020-07-29 22:25:59 +00:00
const pod = application . Pods [ 0 ] ;
_ . forEach ( nodes , ( n ) => {
if ( pod . NodeSelector ) {
const patch = JsonPatch . compare ( n . Labels , pod . NodeSelector ) ;
_ . remove ( patch , { op : 'remove' } ) ;
n . UnmatchedNodeSelectorLabels = _ . map ( patch , ( i ) => {
return { key : _ . trimStart ( i . path , '/' ) , value : i . value } ;
} ) ;
if ( n . UnmatchedNodeSelectorLabels . length ) {
n . AcceptsApplication = false ;
}
}
2020-08-13 23:56:53 +00:00
if ( pod . Affinity . nodeAffinity . requiredDuringSchedulingIgnoredDuringExecution ) {
const unmatchedTerms = _ . map ( pod . Affinity . nodeAffinity . requiredDuringSchedulingIgnoredDuringExecution . nodeSelectorTerms , ( t ) => {
2020-07-29 22:25:59 +00:00
const unmatchedExpressions = _ . map ( t . matchExpressions , ( e ) => {
const exists = { } . hasOwnProperty . call ( n . Labels , e . key ) ;
const isIn = exists && _ . includes ( e . values , n . Labels [ e . key ] ) ;
if (
( e . operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators . EXISTS && exists ) ||
( e . operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators . DOES _NOT _EXIST && ! exists ) ||
( e . operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators . IN && isIn ) ||
( e . operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators . NOT _IN && ! isIn ) ||
2021-02-26 15:50:33 +00:00
( e . operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators . GREATER _THAN && exists && parseInt ( n . Labels [ e . key ] , 10 ) > parseInt ( e . values [ 0 ] , 10 ) ) ||
( e . operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators . LOWER _THAN && exists && parseInt ( n . Labels [ e . key ] , 10 ) < parseInt ( e . values [ 0 ] , 10 ) )
2020-07-29 22:25:59 +00:00
) {
return ;
}
return e ;
} ) ;
return _ . without ( unmatchedExpressions , undefined ) ;
} ) ;
_ . remove ( unmatchedTerms , ( i ) => i . length === 0 ) ;
n . UnmatchedNodeAffinities = unmatchedTerms ;
if ( n . UnmatchedNodeAffinities . length ) {
n . AcceptsApplication = false ;
}
}
} ) ;
return nodes ;
}
function computePlacements ( nodes , application ) {
nodes = computeTolerations ( nodes , application ) ;
nodes = computeAffinities ( nodes , application ) ;
return nodes ;
}
2020-07-05 23:21:03 +00:00
class KubernetesApplicationController {
/* @ngInject */
constructor (
$async ,
$state ,
clipboard ,
Notifications ,
LocalStorage ,
ModalService ,
2022-10-11 21:06:57 +00:00
KubernetesResourcePoolService ,
2020-07-05 23:21:03 +00:00
KubernetesApplicationService ,
KubernetesEventService ,
KubernetesStackService ,
KubernetesPodService ,
2020-07-29 22:25:59 +00:00
KubernetesNodeService ,
2021-09-02 05:28:51 +00:00
StackService
2020-07-05 23:21:03 +00:00
) {
this . $async = $async ;
this . $state = $state ;
this . clipboard = clipboard ;
this . Notifications = Notifications ;
this . LocalStorage = LocalStorage ;
this . ModalService = ModalService ;
2022-10-11 21:06:57 +00:00
this . KubernetesResourcePoolService = KubernetesResourcePoolService ;
2021-09-02 05:28:51 +00:00
this . StackService = StackService ;
2020-07-05 23:21:03 +00:00
this . KubernetesApplicationService = KubernetesApplicationService ;
this . KubernetesEventService = KubernetesEventService ;
this . KubernetesStackService = KubernetesStackService ;
this . KubernetesPodService = KubernetesPodService ;
2020-07-29 22:25:59 +00:00
this . KubernetesNodeService = KubernetesNodeService ;
2020-07-05 23:21:03 +00:00
2020-10-26 18:47:23 +00:00
this . KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes ;
this . KubernetesApplicationTypes = KubernetesApplicationTypes ;
2021-09-07 00:37:26 +00:00
this . KubernetesDeploymentTypes = KubernetesDeploymentTypes ;
2021-04-29 01:10:14 +00:00
2020-07-05 23:21:03 +00:00
this . ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies ;
2020-07-14 20:45:19 +00:00
this . KubernetesServiceTypes = KubernetesServiceTypes ;
2020-08-13 23:27:10 +00:00
this . KubernetesPodContainerTypes = KubernetesPodContainerTypes ;
2020-07-05 23:21:03 +00:00
this . onInit = this . onInit . bind ( this ) ;
this . getApplication = this . getApplication . bind ( this ) ;
this . getApplicationAsync = this . getApplicationAsync . bind ( this ) ;
this . getEvents = this . getEvents . bind ( this ) ;
this . getEventsAsync = this . getEventsAsync . bind ( this ) ;
2021-09-07 00:37:26 +00:00
this . updateApplicationKindText = this . updateApplicationKindText . bind ( this ) ;
2020-07-05 23:21:03 +00:00
this . updateApplicationAsync = this . updateApplicationAsync . bind ( this ) ;
this . redeployApplicationAsync = this . redeployApplicationAsync . bind ( this ) ;
this . rollbackApplicationAsync = this . rollbackApplicationAsync . bind ( this ) ;
this . copyLoadBalancerIP = this . copyLoadBalancerIP . bind ( this ) ;
}
selectTab ( index ) {
this . LocalStorage . storeActiveTab ( 'application' , index ) ;
}
showEditor ( ) {
this . state . showEditorTab = true ;
2020-12-10 06:44:24 +00:00
this . selectTab ( 3 ) ;
2020-07-05 23:21:03 +00:00
}
isSystemNamespace ( ) {
2021-08-26 14:00:59 +00:00
return KubernetesNamespaceHelper . isSystemNamespace ( this . application . ResourcePool ) ;
2020-07-05 23:21:03 +00:00
}
isExternalApplication ( ) {
return KubernetesApplicationHelper . isExternalApplication ( this . application ) ;
}
copyLoadBalancerIP ( ) {
this . clipboard . copyText ( this . application . LoadBalancerIPAddress ) ;
$ ( '#copyNotificationLB' ) . show ( ) . fadeOut ( 2500 ) ;
}
copyApplicationName ( ) {
this . clipboard . copyText ( this . application . Name ) ;
$ ( '#copyNotificationApplicationName' ) . show ( ) . fadeOut ( 2500 ) ;
}
hasPersistedFolders ( ) {
return this . application && this . application . PersistedFolders . length ;
}
hasVolumeConfiguration ( ) {
return this . application && this . application . ConfigurationVolumes . length ;
}
hasEventWarnings ( ) {
return this . state . eventWarningCount ;
}
2020-07-14 20:45:19 +00:00
buildIngressRuleURL ( rule ) {
const hostname = rule . Host ? rule . Host : rule . IP ;
return 'http://' + hostname + rule . Path ;
}
portHasIngressRules ( port ) {
return port . IngressRules . length > 0 ;
}
ruleCanBeDisplayed ( rule ) {
return ! rule . Host && ! rule . IP ? false : true ;
}
2021-09-02 05:28:51 +00:00
isStack ( ) {
return this . application . StackId ;
}
2020-07-05 23:21:03 +00:00
/ * *
* ROLLBACK
* /
async rollbackApplicationAsync ( ) {
try {
// await this.KubernetesApplicationService.rollback(this.application, this.formValues.SelectedRevision);
const revision = _ . nth ( this . application . Revisions , - 2 ) ;
await this . KubernetesApplicationService . rollback ( this . application , revision ) ;
2022-08-10 05:07:35 +00:00
this . Notifications . success ( 'Success' , 'Application successfully rolled back' ) ;
2021-09-24 08:21:50 +00:00
this . $state . reload ( this . $state . current ) ;
2020-07-05 23:21:03 +00:00
} catch ( err ) {
this . Notifications . error ( 'Failure' , err , 'Unable to rollback the application' ) ;
}
}
rollbackApplication ( ) {
2023-01-09 21:02:07 +00:00
this . ModalService . confirmUpdate ( 'Rolling back the application to a previous configuration may cause service interruption. Do you wish to continue?' , ( confirmed ) => {
2020-07-05 23:21:03 +00:00
if ( confirmed ) {
return this . $async ( this . rollbackApplicationAsync ) ;
}
} ) ;
}
/ * *
* REDEPLOY
* /
async redeployApplicationAsync ( ) {
2023-01-09 21:02:07 +00:00
const confirmed = await this . ModalService . confirmAsync ( {
title : 'Are you sure?' ,
message : 'Terminating and restarting the application will cause service interruption. Do you wish to continue?' ,
buttons : { confirm : { label : 'Terminate and restart' , className : 'btn-primary' } } ,
} ) ;
if ( ! confirmed ) {
return ;
}
2020-07-05 23:21:03 +00:00
try {
const promises = _ . map ( this . application . Pods , ( item ) => this . KubernetesPodService . delete ( item ) ) ;
await Promise . all ( promises ) ;
2022-08-10 05:07:35 +00:00
this . Notifications . success ( 'Success' , 'Application successfully redeployed' ) ;
2021-09-24 08:21:50 +00:00
this . $state . reload ( this . $state . current ) ;
2020-07-05 23:21:03 +00:00
} catch ( err ) {
this . Notifications . error ( 'Failure' , err , 'Unable to redeploy the application' ) ;
}
}
redeployApplication ( ) {
2023-01-09 21:02:07 +00:00
return this . $async ( this . redeployApplicationAsync ) ;
2020-07-05 23:21:03 +00:00
}
/ * *
* UPDATE
* /
async updateApplicationAsync ( ) {
try {
const application = angular . copy ( this . application ) ;
application . Note = this . formValues . Note ;
await this . KubernetesApplicationService . patch ( this . application , application , true ) ;
2022-08-10 05:07:35 +00:00
this . Notifications . success ( 'Success' , 'Application successfully updated' ) ;
2021-09-24 08:21:50 +00:00
this . $state . reload ( this . $state . current ) ;
2020-07-05 23:21:03 +00:00
} catch ( err ) {
this . Notifications . error ( 'Failure' , err , 'Unable to update application' ) ;
}
}
updateApplication ( ) {
return this . $async ( this . updateApplicationAsync ) ;
}
2021-09-07 00:37:26 +00:00
updateApplicationKindText ( ) {
if ( this . application . ApplicationKind === this . KubernetesDeploymentTypes . GIT ) {
this . state . appType = ` git repository ` ;
} else if ( this . application . ApplicationKind === this . KubernetesDeploymentTypes . CONTENT ) {
2021-09-29 23:59:19 +00:00
this . state . appType = ` manifest ` ;
} else if ( this . application . ApplicationKind === this . KubernetesDeploymentTypes . URL ) {
this . state . appType = ` manifest ` ;
2021-09-07 00:37:26 +00:00
}
}
2020-07-05 23:21:03 +00:00
/ * *
* EVENTS
* /
async getEventsAsync ( ) {
try {
this . state . eventsLoading = true ;
const events = await this . KubernetesEventService . get ( this . state . params . namespace ) ;
this . events = _ . filter (
events ,
( event ) =>
event . Involved . uid === this . application . Id ||
event . Involved . uid === this . application . ServiceId ||
_ . find ( this . application . Pods , ( pod ) => pod . Id === event . Involved . uid ) !== undefined
) ;
this . state . eventWarningCount = KubernetesEventHelper . warningCount ( this . events ) ;
} catch ( err ) {
this . Notifications . error ( 'Failure' , err , 'Unable to retrieve application related events' ) ;
} finally {
this . state . eventsLoading = false ;
}
}
getEvents ( ) {
return this . $async ( this . getEventsAsync ) ;
}
/ * *
* APPLICATION
* /
async getApplicationAsync ( ) {
try {
this . state . dataLoading = true ;
2020-07-29 22:25:59 +00:00
const [ application , nodes ] = await Promise . all ( [
this . KubernetesApplicationService . get ( this . state . params . namespace , this . state . params . name ) ,
this . KubernetesNodeService . get ( ) ,
] ) ;
this . application = application ;
2020-08-13 23:27:10 +00:00
this . allContainers = KubernetesApplicationHelper . associateAllContainersAndApplication ( application ) ;
2020-07-05 23:21:03 +00:00
this . formValues . Note = this . application . Note ;
2022-01-16 19:37:46 +00:00
this . formValues . Services = this . application . Services ;
2020-07-05 23:21:03 +00:00
if ( this . application . Note ) {
this . state . expandedNote = true ;
}
if ( this . application . CurrentRevision ) {
this . formValues . SelectedRevision = _ . find ( this . application . Revisions , { revision : this . application . CurrentRevision . revision } ) ;
}
2020-07-29 22:25:59 +00:00
2020-08-20 00:51:14 +00:00
this . state . useIngress = _ . find ( application . PublishedPorts , ( p ) => {
return this . portHasIngressRules ( p ) ;
} ) ;
2020-07-29 22:25:59 +00:00
this . placements = computePlacements ( nodes , this . application ) ;
2021-01-20 00:02:18 +00:00
this . state . placementWarning = _ . find ( this . placements , { AcceptsApplication : true } ) ? false : true ;
2021-09-02 05:28:51 +00:00
if ( application . StackId ) {
const file = await this . StackService . getStackFile ( application . StackId ) ;
this . stackFileContent = file ;
}
2020-07-05 23:21:03 +00:00
} catch ( err ) {
this . Notifications . error ( 'Failure' , err , 'Unable to retrieve application details' ) ;
} finally {
this . state . dataLoading = false ;
}
}
getApplication ( ) {
return this . $async ( this . getApplicationAsync ) ;
}
async onInit ( ) {
2022-12-12 01:30:05 +00:00
this . limitedFeature = FeatureId . K8S _ROLLING _RESTART ;
2020-07-05 23:21:03 +00:00
this . state = {
activeTab : 0 ,
currentName : this . $state . $current . name ,
showEditorTab : false ,
DisplayedPanel : 'pods' ,
eventsLoading : true ,
dataLoading : true ,
viewReady : false ,
params : {
namespace : this . $transition$ . params ( ) . namespace ,
name : this . $transition$ . params ( ) . name ,
} ,
2021-09-07 00:37:26 +00:00
appType : this . KubernetesDeploymentTypes . APPLICATION _FORM ,
2020-07-05 23:21:03 +00:00
eventWarningCount : 0 ,
2021-01-20 00:02:18 +00:00
placementWarning : false ,
2020-07-05 23:21:03 +00:00
expandedNote : false ,
2020-08-20 00:51:14 +00:00
useIngress : false ,
2021-12-14 07:34:54 +00:00
useServerMetrics : this . endpoint . Kubernetes . Configuration . UseServerMetrics ,
2022-03-08 12:14:23 +00:00
publicUrl : this . endpoint . PublicURL ,
2020-07-05 23:21:03 +00:00
} ;
this . state . activeTab = this . LocalStorage . getActiveTab ( 'application' ) ;
this . formValues = {
Note : '' ,
SelectedRevision : undefined ,
} ;
2022-10-11 21:06:57 +00:00
const resourcePools = await this . KubernetesResourcePoolService . get ( ) ;
this . allNamespaces = resourcePools . map ( ( { Namespace } ) => Namespace . Name ) ;
2020-07-05 23:21:03 +00:00
await this . getApplication ( ) ;
await this . getEvents ( ) ;
2021-09-07 00:37:26 +00:00
this . updateApplicationKindText ( ) ;
2020-07-05 23:21:03 +00:00
this . state . viewReady = true ;
}
$onInit ( ) {
return this . $async ( this . onInit ) ;
}
$onDestroy ( ) {
if ( this . state . currentName !== this . $state . $current . name ) {
this . LocalStorage . storeActiveTab ( 'application' , 0 ) ;
}
}
}
export default KubernetesApplicationController ;
angular . module ( 'portainer.kubernetes' ) . controller ( 'KubernetesApplicationController' , KubernetesApplicationController ) ;