33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6- import { createReadStream } from 'fs' ;
6+ import { createReadStream , existsSync , writeFileSync } from 'fs' ;
77import { readFile } from 'fs/promises' ;
88import { Promises } from 'vs/base/node/pfs' ;
9+ import { spawn } from 'child_process' ;
10+ import * as fs from 'fs' ;
911import * as path from 'path' ;
1012import * as http from 'http' ;
1113import * as url from 'url' ;
@@ -39,6 +41,10 @@ const textMimeType: { [ext: string]: string | undefined } = {
3941 '.svg' : 'image/svg+xml' ,
4042} ;
4143
44+ const enum ServiceName {
45+ SAGEMAKER_UNIFIED_STUDIO = 'SageMakerUnifiedStudio' ,
46+ }
47+
4248/**
4349 * Return an error to the client.
4450 */
@@ -102,6 +108,7 @@ export class WebClientServer {
102108 private readonly _callbackRoute : string ;
103109 private readonly _webExtensionRoute : string ;
104110 private readonly _idleRoute : string ;
111+ private readonly _postStartupScriptRoute : string ;
105112
106113 constructor (
107114 private readonly _connectionToken : ServerConnectionToken ,
@@ -118,6 +125,7 @@ export class WebClientServer {
118125 this . _callbackRoute = `${ serverRootPath } /callback` ;
119126 this . _webExtensionRoute = `${ serverRootPath } /web-extension-resource` ;
120127 this . _idleRoute = '/api/idle' ;
128+ this . _postStartupScriptRoute = '/api/poststartup' ;
121129 }
122130
123131 /**
@@ -146,6 +154,9 @@ export class WebClientServer {
146154 // extension resource support
147155 return this . _handleWebExtensionResource ( req , res , parsedUrl ) ;
148156 }
157+ if ( pathname === this . _postStartupScriptRoute ) {
158+ return this . _handlePostStartupScriptInvocation ( req , res ) ;
159+ }
149160
150161 return serveError ( req , res , 404 , 'Not found.' ) ;
151162 } catch ( error ) {
@@ -459,12 +470,20 @@ export class WebClientServer {
459470 }
460471
461472 /**
462- * Handles API requests to retrieve the last activity timestamp.
473+ * Handles API requests to retrieve the last activity timestamp.
463474 */
464475 private async _handleIdle ( req : http . IncomingMessage , res : http . ServerResponse ) : Promise < void > {
465476 try {
466477 const tmpDirectory = '/tmp/'
467478 const idleFilePath = path . join ( tmpDirectory , '.sagemaker-last-active-timestamp' ) ;
479+
480+ // If idle shutdown file does not exist, this indicates the app UI may never been opened
481+ // Create the initial metadata file
482+ if ( ! existsSync ( idleFilePath ) ) {
483+ const timestamp = new Date ( ) . toISOString ( ) ;
484+ writeFileSync ( idleFilePath , timestamp ) ;
485+ }
486+
468487 const data = await readFile ( idleFilePath , 'utf8' ) ;
469488
470489 res . statusCode = 200 ;
@@ -474,6 +493,41 @@ export class WebClientServer {
474493 serveError ( req , res , 500 , error . message )
475494 }
476495 }
496+
497+ /**
498+ * Handles API requests to run the post-startup script in SMD.
499+ */
500+ private async _handlePostStartupScriptInvocation ( req : http . IncomingMessage , res : http . ServerResponse ) : Promise < void > {
501+ const postStartupScripPath = '/etc/sagemaker-ui/sagemaker_ui_post_startup.sh'
502+ const logPath = '/var/log/apps/post_startup_default.log' ;
503+ const logStream = fs . createWriteStream ( logPath , { flags : 'a' } ) ;
504+
505+ // Only trigger post-startup script invocation for SageMakerUnifiedStudio app.
506+ if ( process . env [ 'SERVICE_NAME' ] != ServiceName . SAGEMAKER_UNIFIED_STUDIO ) {
507+ return serveError ( req , res , 403 , 'Forbidden' ) ;
508+ } else {
509+ //If postStartupScriptFile doesn't exist, it will throw FileNotFoundError (404)
510+ //If exists, it will start the execution and add the execution logs in logFile.
511+ try {
512+ if ( fs . existsSync ( postStartupScripPath ) ) {
513+ // Adding 0o755 to make script file executable
514+ fs . chmodSync ( postStartupScripPath , 0o755 ) ;
515+
516+ const subprocess = spawn ( 'bash' , [ `${ postStartupScripPath } ` ] , { cwd : '/' } ) ;
517+ subprocess . stdout . pipe ( logStream ) ;
518+ subprocess . stderr . pipe ( logStream ) ;
519+
520+ res . statusCode = 200 ;
521+ res . setHeader ( 'Content-Type' , 'application/json' ) ;
522+ res . end ( JSON . stringify ( { 'success' : 'true' } ) ) ;
523+ } else {
524+ serveError ( req , res , 500 , 'Poststartup script file not found at ' + postStartupScripPath ) ;
525+ }
526+ } catch ( error ) {
527+ serveError ( req , res , 500 , error . message ) ;
528+ }
529+ }
530+ }
477531}
478532
479533/**
0 commit comments