@@ -3,8 +3,7 @@ import Debug from 'debug'
33
44import { getRegions , ObjectStorageKeyRegions , Region , ResourcePage } from '@linode/api-v4'
55import { existsSync , rmSync } from 'fs'
6- import { pathExists , unlink } from 'fs-extra'
7- import { readdir , readFile , writeFile } from 'fs/promises'
6+ import { readFile } from 'fs/promises'
87import { generate as generatePassword } from 'generate-password'
98import { cloneDeep , filter , get , isEmpty , map , merge , omit , pick , set , unset } from 'lodash'
109import { getAppList , getAppSchema , getSecretPaths } from 'src/app'
@@ -117,13 +116,11 @@ import { getAIModels } from './ai/aiModelHandler'
117116import { DatabaseCR } from './ai/DatabaseCR'
118117import { getResourceFilePath , getSecretFilePath } from './fileStore/file-map'
119118import {
120- apply ,
121119 checkPodExists ,
122120 getCloudttyActiveTime ,
123121 getKubernetesVersion ,
124122 getSecretValues ,
125123 getTeamSecretsFromK8s ,
126- k8sdelete ,
127124 watchPodUntilRunning ,
128125} from './k8s_operations'
129126import {
@@ -146,6 +143,7 @@ import {
146143 sparseCloneChart ,
147144 validateGitUrl ,
148145} from './utils/workloadUtils'
146+ import CloudTty from './tty'
149147
150148interface ExcludedApp extends App {
151149 managed : boolean
@@ -208,12 +206,20 @@ export default class OtomiStack {
208206 isLoaded = false
209207 git : Git
210208 fileStore : FileStore
209+ private cloudTty : CloudTty
211210
212211 constructor ( editor ?: string , sessionId ?: string ) {
213212 this . editor = editor
214213 this . sessionId = sessionId ?? 'main'
215214 }
216215
216+ getCloudTty ( ) {
217+ if ( ! this . cloudTty ) {
218+ this . cloudTty = new CloudTty ( )
219+ }
220+ return this . cloudTty
221+ }
222+
217223 getAppList ( ) {
218224 let apps = getAppList ( )
219225 apps = apps . filter ( ( item ) => item !== 'ingress-nginx' )
@@ -1524,11 +1530,12 @@ export default class OtomiStack {
15241530 }
15251531
15261532 async connectCloudtty ( teamId : string , sessionUser : SessionUser ) : Promise < Cloudtty > {
1533+ const isAdmin = sessionUser . isPlatformAdmin
1534+ const targetNamespace = isAdmin ? 'team-admin' : `team-${ teamId } `
15271535 if ( ! sessionUser . sub ) {
15281536 debug ( 'No user sub found, cannot connect to shell.' )
15291537 throw new OtomiError ( 500 , 'No user sub found, cannot connect to shell.' )
15301538 }
1531- const userTeams = sessionUser . teams . map ( ( teamName ) => `team-${ teamName } ` )
15321539 const variables = {
15331540 FQDN : '' ,
15341541 SUB : sessionUser . sub ,
@@ -1545,62 +1552,20 @@ export default class OtomiStack {
15451552 }
15461553
15471554 // if cloudtty shell does not exists then check if the pod is running and return it
1548- if ( await checkPodExists ( 'team-admin' , `tty-${ sessionUser . sub } ` ) ) {
1555+ if ( await checkPodExists ( targetNamespace , `tty-${ sessionUser . sub } ` ) ) {
15491556 return { iFrameUrl : `https://tty.${ variables . FQDN } /${ sessionUser . sub } ` }
15501557 }
15511558
1552- if ( await pathExists ( '/tmp/ttyd.yaml' ) ) await unlink ( '/tmp/ttyd.yaml' )
1553-
1554- //if user is admin then read the manifests from ./dist/src/ttyManifests/adminTtyManifests
1555- const files = sessionUser . isPlatformAdmin
1556- ? await readdir ( './dist/src/ttyManifests/adminTtyManifests' , 'utf-8' )
1557- : await readdir ( './dist/src/ttyManifests' , 'utf-8' )
1558- const filteredFiles = files . filter ( ( file ) => file . startsWith ( 'tty' ) )
1559- const variableKeys = Object . keys ( variables )
1560-
1561- const podContentAddTargetTeam = ( fileContent ) => {
1562- const regex = new RegExp ( `\\$TARGET_TEAM` , 'g' )
1563- return fileContent . replace ( regex , teamId )
1564- }
1565-
1566- // iterates over the rolebinding file and replace the $TARGET_TEAM with the team name for teams
1567- const rolebindingContentsForUsers = ( fileContent ) => {
1568- const rolebindingArray : string [ ] = [ ]
1569- userTeams ?. forEach ( ( team : string ) => {
1570- const regex = new RegExp ( `\\$TARGET_TEAM` , 'g' )
1571- const rolebindingForTeam : string = fileContent . replace ( regex , team )
1572- rolebindingArray . push ( rolebindingForTeam )
1573- } )
1574- return rolebindingArray . join ( '\n' )
1575- }
1576-
1577- const fileContents = await Promise . all (
1578- filteredFiles . map ( async ( file ) => {
1579- let fileContent = sessionUser . isPlatformAdmin
1580- ? await readFile ( `./dist/src/ttyManifests/adminTtyManifests/${ file } ` , 'utf-8' )
1581- : await readFile ( `./dist/src/ttyManifests/${ file } ` , 'utf-8' )
1582- variableKeys . forEach ( ( key ) => {
1583- const regex = new RegExp ( `\\$${ key } ` , 'g' )
1584- fileContent = fileContent . replace ( regex , variables [ key ] as string )
1585- } )
1586- if ( file === 'tty_02_Pod.yaml' ) fileContent = podContentAddTargetTeam ( fileContent )
1587- if ( ! sessionUser . isPlatformAdmin && file === 'tty_03_Rolebinding.yaml' ) {
1588- fileContent = rolebindingContentsForUsers ( fileContent )
1589- }
1590- return fileContent
1591- } ) ,
1592- )
1593- await writeFile ( '/tmp/ttyd.yaml' , fileContents , 'utf-8' )
1594- await apply ( '/tmp/ttyd.yaml' )
1595- await watchPodUntilRunning ( 'team-admin' , `tty-${ sessionUser . sub } ` )
1559+ await this . getCloudTty ( ) . createTty ( teamId , sessionUser , variables . FQDN )
1560+ await watchPodUntilRunning ( targetNamespace , `tty-${ sessionUser . sub } ` )
15961561
15971562 // check the pod every 30 minutes and terminate it after 2 hours of inactivity
15981563 const ISACTIVE_INTERVAL = 30 * 60 * 1000
15991564 const TERMINATE_TIMEOUT = 2 * 60 * 60 * 1000
16001565 const intervalId = setInterval ( ( ) => {
1601- getCloudttyActiveTime ( 'team-admin' , `tty-${ sessionUser . sub } ` ) . then ( ( activeTime : number ) => {
1566+ getCloudttyActiveTime ( targetNamespace , `tty-${ sessionUser . sub } ` ) . then ( async ( activeTime : number ) => {
16021567 if ( activeTime > TERMINATE_TIMEOUT ) {
1603- this . deleteCloudtty ( sessionUser )
1568+ await this . getCloudTty ( ) . deleteTty ( teamId , sessionUser )
16041569 clearInterval ( intervalId )
16051570 debug ( `Cloudtty terminated after ${ TERMINATE_TIMEOUT / ( 60 * 60 * 1000 ) } hours of inactivity` )
16061571 }
@@ -1610,16 +1575,8 @@ export default class OtomiStack {
16101575 return { iFrameUrl : `https://tty.${ variables . FQDN } /${ sessionUser . sub } ` }
16111576 }
16121577
1613- async deleteCloudtty ( sessionUser : SessionUser ) : Promise < void > {
1614- const { sub, isPlatformAdmin, teams } = sessionUser as { sub : string ; isPlatformAdmin : boolean ; teams : string [ ] }
1615- const userTeams = teams . map ( ( teamName ) => `team-${ teamName } ` )
1616- try {
1617- if ( await checkPodExists ( 'team-admin' , `tty-${ sessionUser . sub } ` ) ) {
1618- await k8sdelete ( { sub, isPlatformAdmin, userTeams } )
1619- }
1620- } catch ( error ) {
1621- debug ( 'Failed to delete cloudtty' )
1622- }
1578+ async deleteCloudtty ( teamId : string , sessionUser : SessionUser ) : Promise < void > {
1579+ await this . getCloudTty ( ) . deleteTty ( teamId , sessionUser )
16231580 }
16241581
16251582 private async fetchCatalog (
0 commit comments