2020-07-05 23:21:03 +00:00
import angular from 'angular' ;
import _ from 'lodash-es' ;
import stripAnsi from 'strip-ansi' ;
2022-05-31 10:00:47 +00:00
import PortainerError from '@/portainer/error' ;
2021-09-29 23:58:10 +00:00
import { KubernetesDeployManifestTypes , KubernetesDeployBuildMethods , KubernetesDeployRequestMethods , RepositoryMechanismTypes } from 'Kubernetes/models/deploy' ;
2023-11-15 08:45:07 +00:00
import { isTemplateVariablesEnabled , renderTemplate } from '@/react/portainer/custom-templates/components/utils' ;
2023-10-16 01:08:06 +00:00
import { getDeploymentOptions } from '@/react/portainer/environments/environment.service' ;
2023-01-26 03:03:44 +00:00
import { kubernetes } from '@@/BoxSelector/common-options/deployment-methods' ;
2023-10-08 22:20:44 +00:00
import { editor , git , customTemplate , url , helm } from '@@/BoxSelector/common-options/build-methods' ;
2023-02-22 20:13:33 +00:00
import { parseAutoUpdateResponse , transformAutoUpdateViewModel } from '@/react/portainer/gitops/AutoUpdateFieldset/utils' ;
2023-03-02 15:07:50 +00:00
import { baseStackWebhookUrl , createWebhookId } from '@/portainer/helpers/webhookHelper' ;
2023-02-14 08:19:41 +00:00
import { confirmWebEditorDiscard } from '@@/modals/confirm' ;
2023-11-15 08:45:07 +00:00
import { getVariablesFieldDefaultValues } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField' ;
2024-06-16 21:24:54 +00:00
import { KUBE _STACK _NAME _VALIDATION _REGEX } from '@/react/kubernetes/DeployView/StackName/constants' ;
2021-12-14 19:14:53 +00:00
2020-07-05 23:21:03 +00:00
class KubernetesDeployController {
/* @ngInject */
2024-02-11 21:54:29 +00:00
constructor ( $async , $state , $window , Authentication , Notifications , KubernetesResourcePoolService , StackService , CustomTemplateService , KubernetesApplicationService ) {
2020-07-05 23:21:03 +00:00
this . $async = $async ;
this . $state = $state ;
2021-03-20 21:13:27 +00:00
this . $window = $window ;
2021-09-05 10:03:48 +00:00
this . Authentication = Authentication ;
2020-07-05 23:21:03 +00:00
this . Notifications = Notifications ;
this . KubernetesResourcePoolService = KubernetesResourcePoolService ;
this . StackService = StackService ;
2021-09-29 23:58:10 +00:00
this . CustomTemplateService = CustomTemplateService ;
2024-02-11 21:54:29 +00:00
this . KubernetesApplicationService = KubernetesApplicationService ;
2020-07-05 23:21:03 +00:00
2023-11-15 08:45:07 +00:00
this . isTemplateVariablesEnabled = isTemplateVariablesEnabled ;
2022-06-16 05:32:41 +00:00
2022-12-01 00:46:23 +00:00
this . deployOptions = [ { ... kubernetes , value : KubernetesDeployManifestTypes . KUBERNETES } ] ;
2021-08-18 11:56:13 +00:00
this . methodOptions = [
2022-08-22 08:55:48 +00:00
{ ... git , value : KubernetesDeployBuildMethods . GIT } ,
{ ... editor , value : KubernetesDeployBuildMethods . WEB _EDITOR } ,
{ ... url , value : KubernetesDeployBuildMethods . URL } ,
2024-02-15 23:34:06 +00:00
{ ... customTemplate , value : KubernetesDeployBuildMethods . CUSTOM _TEMPLATE } ,
2023-10-08 22:20:44 +00:00
{ ... helm , value : KubernetesDeployBuildMethods . HELM } ,
2021-08-18 11:56:13 +00:00
] ;
2023-10-08 22:20:44 +00:00
let buildMethod = Number ( this . $state . params . buildMethod ) || KubernetesDeployBuildMethods . GIT ;
if ( buildMethod > Object . keys ( KubernetesDeployBuildMethods ) . length ) {
buildMethod = KubernetesDeployBuildMethods . GIT ;
}
2021-08-18 11:56:13 +00:00
this . state = {
2023-10-08 22:20:44 +00:00
DeployType : buildMethod ,
2021-08-18 11:56:13 +00:00
BuildMethod : KubernetesDeployBuildMethods . GIT ,
tabLogsDisabled : true ,
activeTab : 0 ,
viewReady : false ,
isEditorDirty : false ,
2021-09-02 05:28:51 +00:00
templateId : null ,
2022-05-31 10:00:47 +00:00
template : null ,
2023-02-22 20:13:33 +00:00
baseWebhookUrl : baseStackWebhookUrl ( ) ,
2023-03-02 15:07:50 +00:00
webhookId : createWebhookId ( ) ,
2023-04-04 00:44:42 +00:00
templateLoadFailed : false ,
isEditorReadOnly : false ,
2023-10-11 22:02:09 +00:00
selectedHelmChart : '' ,
2024-06-16 21:24:54 +00:00
stackNameError : '' ,
2023-04-04 00:44:42 +00:00
} ;
this . currentUser = {
isAdmin : false ,
id : null ,
2021-08-18 11:56:13 +00:00
} ;
2021-09-29 23:58:10 +00:00
this . formValues = {
StackName : '' ,
RepositoryURL : '' ,
RepositoryReferenceName : '' ,
2022-01-15 16:44:20 +00:00
RepositoryAuthentication : false ,
2021-09-29 23:58:10 +00:00
RepositoryUsername : '' ,
RepositoryPassword : '' ,
AdditionalFiles : [ ] ,
2021-10-06 13:12:36 +00:00
ComposeFilePathInRepository : '' ,
2023-11-15 08:45:07 +00:00
Variables : [ ] ,
2023-02-22 20:13:33 +00:00
AutoUpdate : parseAutoUpdateResponse ( ) ,
2023-04-03 06:19:17 +00:00
TLSSkipVerify : false ,
2023-10-17 22:37:03 +00:00
Name : '' ,
2021-09-29 23:58:10 +00:00
} ;
2021-11-22 20:51:02 +00:00
2024-02-11 21:54:29 +00:00
this . stacks = [ ] ;
2021-08-18 11:56:13 +00:00
this . ManifestDeployTypes = KubernetesDeployManifestTypes ;
this . BuildMethods = KubernetesDeployBuildMethods ;
2023-10-11 22:02:09 +00:00
this . onSelectHelmChart = this . onSelectHelmChart . bind ( this ) ;
2021-09-02 05:28:51 +00:00
this . onChangeTemplateId = this . onChangeTemplateId . bind ( this ) ;
2020-07-05 23:21:03 +00:00
this . deployAsync = this . deployAsync . bind ( this ) ;
2021-08-18 11:56:13 +00:00
this . onChangeFileContent = this . onChangeFileContent . bind ( this ) ;
2020-07-05 23:21:03 +00:00
this . getNamespacesAsync = this . getNamespacesAsync . bind ( this ) ;
2021-08-18 11:56:13 +00:00
this . onChangeFormValues = this . onChangeFormValues . bind ( this ) ;
2021-09-05 10:03:48 +00:00
this . buildAnalyticsProperties = this . buildAnalyticsProperties . bind ( this ) ;
2021-12-14 19:14:53 +00:00
this . onChangeMethod = this . onChangeMethod . bind ( this ) ;
this . onChangeDeployType = this . onChangeDeployType . bind ( this ) ;
2022-05-31 10:00:47 +00:00
this . onChangeTemplateVariables = this . onChangeTemplateVariables . bind ( this ) ;
2023-10-16 01:08:06 +00:00
this . setStackName = this . setStackName . bind ( this ) ;
2024-02-11 21:54:29 +00:00
this . onChangeNamespace = this . onChangeNamespace . bind ( this ) ;
}
onChangeNamespace ( ) {
return this . $async ( async ( ) => {
const applications = await this . KubernetesApplicationService . get ( this . formValues . Namespace ) ;
const stacks = _ . map ( applications , ( item ) => item . StackName ) . filter ( ( item ) => item !== '' ) ;
this . stacks = _ . uniq ( stacks ) ;
} ) ;
2022-05-31 10:00:47 +00:00
}
2023-10-11 22:02:09 +00:00
onSelectHelmChart ( chart ) {
this . state . selectedHelmChart = chart ;
}
2022-05-31 10:00:47 +00:00
onChangeTemplateVariables ( value ) {
this . onChangeFormValues ( { Variables : value } ) ;
this . renderTemplate ( ) ;
}
2023-10-16 01:08:06 +00:00
setStackName ( name ) {
2024-06-16 21:24:54 +00:00
return this . $async ( async ( ) => {
if ( KUBE _STACK _NAME _VALIDATION _REGEX . test ( name ) || name === '' ) {
this . state . stackNameError = '' ;
} else {
this . state . stackNameError =
"Stack must consist of alphanumeric characters, '-', '_' or '.', must start and end with an alphanumeric character and must be 63 characters or less (e.g. 'my-name', or 'abc-123')." ;
}
this . formValues . StackName = name ;
} ) ;
2023-10-16 01:08:06 +00:00
}
2022-05-31 10:00:47 +00:00
renderTemplate ( ) {
2022-06-16 05:32:41 +00:00
if ( ! this . isTemplateVariablesEnabled ) {
return ;
}
2022-05-31 10:00:47 +00:00
const rendered = renderTemplate ( this . state . templateContent , this . formValues . Variables , this . state . template . Variables ) ;
this . onChangeFormValues ( { EditorContent : rendered } ) ;
2021-09-05 10:03:48 +00:00
}
buildAnalyticsProperties ( ) {
const metadata = {
type : buildLabel ( this . state . BuildMethod ) ,
format : formatLabel ( this . state . DeployType ) ,
2023-04-04 00:44:42 +00:00
role : roleLabel ( this . currentUser . isAdmin ) ,
2021-09-29 23:58:10 +00:00
'automatic-updates' : automaticUpdatesLabel ( this . formValues . RepositoryAutomaticUpdates , this . formValues . RepositoryMechanism ) ,
2021-09-05 10:03:48 +00:00
} ;
if ( this . state . BuildMethod === KubernetesDeployBuildMethods . GIT ) {
metadata . auth = this . formValues . RepositoryAuthentication ;
}
return { metadata } ;
2021-09-29 23:58:10 +00:00
function automaticUpdatesLabel ( repositoryAutomaticUpdates , repositoryMechanism ) {
switch ( repositoryAutomaticUpdates && repositoryMechanism ) {
case RepositoryMechanismTypes . INTERVAL :
return 'polling' ;
case RepositoryMechanismTypes . WEBHOOK :
return 'webhook' ;
default :
return 'off' ;
}
}
2021-09-05 10:03:48 +00:00
function roleLabel ( isAdmin ) {
if ( isAdmin ) {
return 'admin' ;
}
return 'standard' ;
}
function buildLabel ( buildMethod ) {
switch ( buildMethod ) {
case KubernetesDeployBuildMethods . GIT :
return 'git' ;
case KubernetesDeployBuildMethods . WEB _EDITOR :
return 'web-editor' ;
}
}
function formatLabel ( format ) {
switch ( format ) {
case KubernetesDeployManifestTypes . COMPOSE :
return 'compose' ;
case KubernetesDeployManifestTypes . KUBERNETES :
return 'manifest' ;
}
}
2020-07-05 23:21:03 +00:00
}
2021-12-14 19:14:53 +00:00
onChangeMethod ( method ) {
2023-02-22 20:13:33 +00:00
return this . $async ( async ( ) => {
this . state . BuildMethod = method ;
} ) ;
2021-12-14 19:14:53 +00:00
}
onChangeDeployType ( type ) {
2023-02-22 20:13:33 +00:00
return this . $async ( async ( ) => {
this . state . DeployType = type ;
} ) ;
2021-12-14 19:14:53 +00:00
}
2020-07-05 23:21:03 +00:00
disableDeploy ( ) {
2023-11-01 19:50:12 +00:00
const isWebEditorInvalid = this . state . BuildMethod === KubernetesDeployBuildMethods . WEB _EDITOR && _ . isEmpty ( this . formValues . EditorContent ) ;
2023-11-09 00:26:42 +00:00
const isURLFormInvalid = this . state . BuildMethod === KubernetesDeployBuildMethods . URL && _ . isEmpty ( this . formValues . ManifestURL ) ;
2023-11-02 22:39:44 +00:00
const isCustomTemplateInvalid = this . state . BuildMethod === KubernetesDeployBuildMethods . CUSTOM _TEMPLATE && _ . isEmpty ( this . formValues . EditorContent ) ;
2021-09-22 04:01:28 +00:00
const isNamespaceInvalid = _ . isEmpty ( this . formValues . Namespace ) ;
2024-06-16 21:24:54 +00:00
const isStackNameInvalid = this . state . stackNameError !== '' ;
return isWebEditorInvalid || isURLFormInvalid || isCustomTemplateInvalid || this . state . actionInProgress || isNamespaceInvalid || isStackNameInvalid ;
2020-07-05 23:21:03 +00:00
}
2023-02-22 20:13:33 +00:00
onChangeFormValues ( newValues ) {
return this . $async ( async ( ) => {
this . formValues = {
... this . formValues ,
... newValues ,
} ;
} ) ;
2020-07-05 23:21:03 +00:00
}
2022-05-31 10:00:47 +00:00
onChangeTemplateId ( templateId , template ) {
2021-09-02 05:28:51 +00:00
return this . $async ( async ( ) => {
2022-05-31 10:00:47 +00:00
if ( ! template || ( this . state . templateId === templateId && this . state . template === template ) ) {
2021-09-02 05:28:51 +00:00
return ;
}
this . state . templateId = templateId ;
2022-05-31 10:00:47 +00:00
this . state . template = template ;
2021-09-02 05:28:51 +00:00
try {
2023-04-04 00:44:42 +00:00
try {
this . state . templateContent = await this . CustomTemplateService . customTemplateFile ( templateId , template . GitConfig !== null ) ;
this . onChangeFileContent ( this . state . templateContent ) ;
2023-12-07 12:42:18 +00:00
this . state . isEditorReadOnly = false ;
2023-04-04 00:44:42 +00:00
} catch ( err ) {
this . state . templateLoadFailed = true ;
throw err ;
}
2022-05-31 10:00:47 +00:00
if ( template . Variables && template . Variables . length > 0 ) {
2023-11-15 08:45:07 +00:00
const variables = getVariablesFieldDefaultValues ( template . Variables ) ;
2022-05-31 10:00:47 +00:00
this . onChangeTemplateVariables ( variables ) ;
}
2021-09-02 05:28:51 +00:00
} catch ( err ) {
this . Notifications . error ( 'Failure' , err , 'Unable to load template file' ) ;
}
} ) ;
}
2021-08-18 11:56:13 +00:00
onChangeFileContent ( value ) {
this . formValues . EditorContent = value ;
this . state . isEditorDirty = true ;
2020-07-05 23:21:03 +00:00
}
displayErrorLog ( log ) {
this . errorLog = stripAnsi ( log ) ;
this . state . tabLogsDisabled = false ;
this . state . activeTab = 1 ;
}
async deployAsync ( ) {
this . errorLog = '' ;
this . state . actionInProgress = true ;
try {
2021-09-03 05:37:34 +00:00
let method ;
2021-09-02 05:28:51 +00:00
let composeFormat = this . state . DeployType === this . ManifestDeployTypes . COMPOSE ;
switch ( this . state . BuildMethod ) {
2021-09-03 05:37:34 +00:00
case this . BuildMethods . GIT :
2021-09-02 05:28:51 +00:00
method = KubernetesDeployRequestMethods . REPOSITORY ;
break ;
2021-09-03 05:37:34 +00:00
case this . BuildMethods . WEB _EDITOR :
method = KubernetesDeployRequestMethods . STRING ;
break ;
2021-09-02 05:28:51 +00:00
case KubernetesDeployBuildMethods . CUSTOM _TEMPLATE :
2021-09-03 05:37:34 +00:00
method = KubernetesDeployRequestMethods . STRING ;
2021-09-02 05:28:51 +00:00
composeFormat = false ;
2021-09-05 10:03:48 +00:00
break ;
2021-09-03 05:37:34 +00:00
case this . BuildMethods . URL :
method = KubernetesDeployRequestMethods . URL ;
2021-09-02 05:28:51 +00:00
break ;
2021-09-03 05:37:34 +00:00
default :
throw new PortainerError ( 'Unable to determine build method' ) ;
2021-09-02 05:28:51 +00:00
}
2021-06-16 21:47:32 +00:00
2021-11-22 20:51:02 +00:00
let deployNamespace = '' ;
2022-03-24 08:28:53 +00:00
if ( this . formValues . namespace _toggle ) {
deployNamespace = '' ;
} else {
2021-11-22 20:51:02 +00:00
deployNamespace = this . formValues . Namespace ;
}
2021-06-16 21:47:32 +00:00
const payload = {
2021-09-02 05:28:51 +00:00
ComposeFormat : composeFormat ,
2021-11-22 20:51:02 +00:00
Namespace : deployNamespace ,
2021-09-29 23:58:10 +00:00
StackName : this . formValues . StackName ,
2021-06-16 21:47:32 +00:00
} ;
if ( method === KubernetesDeployRequestMethods . REPOSITORY ) {
2023-04-03 06:19:17 +00:00
payload . TLSSkipVerify = this . formValues . TLSSkipVerify ;
2021-06-16 21:47:32 +00:00
payload . RepositoryURL = this . formValues . RepositoryURL ;
payload . RepositoryReferenceName = this . formValues . RepositoryReferenceName ;
payload . RepositoryAuthentication = this . formValues . RepositoryAuthentication ? true : false ;
if ( payload . RepositoryAuthentication ) {
payload . RepositoryUsername = this . formValues . RepositoryUsername ;
payload . RepositoryPassword = this . formValues . RepositoryPassword ;
}
2021-09-29 23:58:10 +00:00
payload . ManifestFile = this . formValues . ComposeFilePathInRepository ;
payload . AdditionalFiles = this . formValues . AdditionalFiles ;
2023-03-02 15:07:50 +00:00
payload . AutoUpdate = transformAutoUpdateViewModel ( this . formValues . AutoUpdate , this . state . webhookId ) ;
2021-09-03 05:37:34 +00:00
} else if ( method === KubernetesDeployRequestMethods . STRING ) {
2021-06-16 21:47:32 +00:00
payload . StackFileContent = this . formValues . EditorContent ;
2021-09-03 05:37:34 +00:00
} else {
payload . ManifestURL = this . formValues . ManifestURL ;
2021-06-16 21:47:32 +00:00
}
2021-12-14 07:34:54 +00:00
await this . StackService . kubernetesDeploy ( this . endpoint . Id , method , payload ) ;
2021-06-16 21:47:32 +00:00
2023-04-30 21:14:30 +00:00
this . Notifications . success ( 'Success' , 'Request to deploy manifest successfully submitted' ) ;
2021-03-20 21:13:27 +00:00
this . state . isEditorDirty = false ;
2023-04-25 23:23:15 +00:00
2023-06-11 21:46:48 +00:00
if ( this . $state . params . referrer && this . $state . params . tab ) {
this . $state . go ( this . $state . params . referrer , { tab : this . $state . params . tab } ) ;
return ;
}
2023-04-25 23:23:15 +00:00
if ( this . $state . params . referrer ) {
this . $state . go ( this . $state . params . referrer ) ;
return ;
}
2020-07-05 23:21:03 +00:00
this . $state . go ( 'kubernetes.applications' ) ;
} catch ( err ) {
this . Notifications . error ( 'Unable to deploy manifest' , err , 'Unable to deploy resources' ) ;
this . displayErrorLog ( err . err . data . details ) ;
} finally {
this . state . actionInProgress = false ;
}
}
deploy ( ) {
return this . $async ( this . deployAsync ) ;
}
async getNamespacesAsync ( ) {
try {
const pools = await this . KubernetesResourcePoolService . get ( ) ;
2023-03-15 21:10:37 +00:00
let namespaces = pools . filter ( ( pool ) => pool . Namespace . Status === 'Active' ) ;
namespaces = _ . map ( namespaces , 'Namespace' ) . sort ( ( a , b ) => {
2021-03-22 22:35:17 +00:00
if ( a . Name === 'default' ) {
return - 1 ;
}
if ( b . Name === 'default' ) {
return 1 ;
}
return 0 ;
} ) ;
this . namespaces = namespaces ;
2021-09-22 04:01:28 +00:00
if ( this . namespaces . length > 0 ) {
this . formValues . Namespace = this . namespaces [ 0 ] . Name ;
}
2020-07-05 23:21:03 +00:00
} catch ( err ) {
2021-04-27 08:12:34 +00:00
this . Notifications . error ( 'Failure' , err , 'Unable to load namespaces data' ) ;
2020-07-05 23:21:03 +00:00
}
}
getNamespaces ( ) {
return this . $async ( this . getNamespacesAsync ) ;
}
2021-03-20 21:13:27 +00:00
async uiCanExit ( ) {
if ( this . formValues . EditorContent && this . state . isEditorDirty ) {
2023-02-14 08:19:41 +00:00
return confirmWebEditorDiscard ( ) ;
2021-03-20 21:13:27 +00:00
}
}
2021-09-02 05:28:51 +00:00
$onInit ( ) {
return this . $async ( async ( ) => {
2023-04-04 00:44:42 +00:00
this . currentUser . isAdmin = this . Authentication . isAdmin ( ) ;
this . currentUser . id = this . Authentication . getUserDetails ( ) . ID ;
2022-03-24 08:28:53 +00:00
this . formValues . namespace _toggle = false ;
2021-09-02 05:28:51 +00:00
await this . getNamespaces ( ) ;
2023-10-16 01:08:06 +00:00
this . deploymentOptions = await getDeploymentOptions ( this . endpoint . Id ) ;
2021-09-02 05:28:51 +00:00
if ( this . $state . params . templateId ) {
const templateId = parseInt ( this . $state . params . templateId , 10 ) ;
if ( templateId && ! Number . isNaN ( templateId ) ) {
this . state . BuildMethod = KubernetesDeployBuildMethods . CUSTOM _TEMPLATE ;
2022-05-31 10:00:47 +00:00
this . state . templateId = templateId ;
2021-09-02 05:28:51 +00:00
}
2021-03-20 21:13:27 +00:00
}
2020-07-05 23:21:03 +00:00
2024-02-11 21:54:29 +00:00
this . onChangeNamespace ( ) ;
2021-09-02 05:28:51 +00:00
this . state . viewReady = true ;
this . $window . onbeforeunload = ( ) => {
if ( this . formValues . EditorContent && this . state . isEditorDirty ) {
return '' ;
}
} ;
} ) ;
2020-07-05 23:21:03 +00:00
}
2021-08-10 04:44:33 +00:00
$onDestroy ( ) {
this . state . isEditorDirty = false ;
}
2020-07-05 23:21:03 +00:00
}
export default KubernetesDeployController ;
angular . module ( 'portainer.kubernetes' ) . controller ( 'KubernetesDeployController' , KubernetesDeployController ) ;