11
11
12
12
"use strict" ;
13
13
14
- import { IHandlerParameters , AbstractSession , ITaskWithStatus , TaskStage , TaskProgress , Logger , IProfile } from "@zowe/imperative" ;
14
+ import { IHandlerParameters , AbstractSession , ITaskWithStatus , TaskStage , TaskProgress , Logger , IProfile , Session } from "@zowe/imperative" ;
15
15
import { List , ZosmfSession , SshSession , Shell , Upload , IUploadOptions , ZosFilesAttributes , Create } from "@zowe/cli" ;
16
+ import { getResource , IResourceParms } from "@zowe/cics" ;
16
17
import { BundleDeployer } from "../BundleDeploy/BundleDeployer" ;
17
18
import { Bundle } from "../BundleContent/Bundle" ;
18
19
@@ -70,14 +71,17 @@ export class BundlePusher {
70
71
}
71
72
72
73
// Get the profiles
73
- const zosMFProfile = this . getProfile ( "zosmf" ) ;
74
- const sshProfile = this . getProfile ( "ssh" ) ;
75
- this . validateProfiles ( zosMFProfile , sshProfile ) ;
74
+ const zosMFProfile = this . getProfile ( "zosmf" , true ) ;
75
+ const sshProfile = this . getProfile ( "ssh" , true ) ;
76
+ const cicsProfile = this . getProfile ( "cics" , false ) ;
77
+ this . validateProfiles ( zosMFProfile , sshProfile , cicsProfile ) ;
76
78
77
79
// Create a zOSMF session
78
80
const zosMFSession = await this . createZosMFSession ( zosMFProfile ) ;
79
81
// Create an SSH session
80
82
const sshSession = await this . createSshSession ( sshProfile ) ;
83
+ // If relevant, start a CICS session
84
+ const cicsSession = await this . createCicsSession ( cicsProfile ) ;
81
85
82
86
// Start a progress bar (but only in non-verbose mode)
83
87
this . progressBar = { percentComplete : 0 ,
@@ -125,7 +129,7 @@ export class BundlePusher {
125
129
await this . runAllNpmInstalls ( sshSession , packageJsonFiles ) ;
126
130
127
131
// Run DFHDPLOY to install the bundle
128
- await this . deployBundle ( zosMFSession , bd ) ;
132
+ await this . deployBundle ( zosMFSession , bd , cicsSession ) ;
129
133
130
134
// Complete the progress bar
131
135
this . progressBar . percentComplete = TaskProgress . ONE_HUNDRED_PERCENT ;
@@ -181,10 +185,10 @@ export class BundlePusher {
181
185
}
182
186
}
183
187
184
- private getProfile ( type : string ) : IProfile {
188
+ private getProfile ( type : string , required : boolean ) : IProfile {
185
189
const profile = this . params . profiles . get ( type ) ;
186
190
187
- if ( profile === undefined ) {
191
+ if ( required && profile === undefined ) {
188
192
throw new Error ( "No " + type + " profile found" ) ;
189
193
}
190
194
@@ -193,32 +197,86 @@ export class BundlePusher {
193
197
194
198
private issueWarning ( msg : string ) {
195
199
const warningMsg = "WARNING: " + msg + "\n" ;
200
+ this . issueMessage ( warningMsg ) ;
201
+ }
202
+
203
+ private issueMessage ( msg : string ) {
196
204
this . endProgressBar ( ) ;
197
- this . params . response . console . log ( Buffer . from ( warningMsg ) ) ;
205
+ this . params . response . console . log ( Buffer . from ( msg ) ) ;
198
206
if ( this . params . arguments . silent === undefined ) {
199
207
const logger = Logger . getAppLogger ( ) ;
200
- logger . warn ( warningMsg ) ;
208
+ logger . warn ( msg ) ;
201
209
}
202
210
this . startProgressBar ( ) ;
203
211
}
204
212
205
- private validateProfiles ( zosmfProfile : IProfile , sshProfile : IProfile ) {
206
- // Do they share the same host name?
213
+ private validateProfiles ( zosmfProfile : IProfile , sshProfile : IProfile , cicsProfile : IProfile ) {
214
+ // Do the required profiles share the same host name?
207
215
if ( zosmfProfile . host !== sshProfile . host ) {
208
216
this . issueWarning ( "ssh profile --host value '" + sshProfile . host + "' does not match zosmf value '" + zosmfProfile . host + "'." ) ;
209
217
}
210
- // Do they share the same user name?
218
+ // Do the required profiles share the same user name?
211
219
if ( zosmfProfile . user !== sshProfile . user ) {
212
220
this . issueWarning ( "ssh profile --user value '" + sshProfile . user + "' does not match zosmf value '" + zosmfProfile . user + "'." ) ;
213
221
}
222
+
223
+ // Is the optional CICS profile compatible?
224
+ if ( cicsProfile !== undefined ) {
225
+ if ( zosmfProfile . host !== cicsProfile . host ) {
226
+ this . issueWarning ( "cics profile --host value '" + cicsProfile . host + "' does not match zosmf value '" + zosmfProfile . host + "'." ) ;
227
+ }
228
+ if ( zosmfProfile . user !== cicsProfile . user ) {
229
+ this . issueWarning ( "cics profile --user value '" + cicsProfile . user + "' does not match zosmf value '" + zosmfProfile . user + "'." ) ;
230
+ }
231
+
232
+ // Do the cics-plexes match?
233
+ if ( this . params . arguments . cicsplex !== cicsProfile . cicsPlex ) {
234
+ this . issueWarning ( "cics profile --cics-plex value '" + cicsProfile . cicsPlex +
235
+ "' does not match --cicsplex value '" + this . params . arguments . cicsplex + "'." ) ;
236
+ }
237
+ }
214
238
}
215
239
216
240
private async createZosMFSession ( zosmfProfile : IProfile ) : Promise < AbstractSession > {
217
- return ZosmfSession . createBasicZosmfSession ( zosmfProfile ) ;
241
+ try {
242
+ return ZosmfSession . createBasicZosmfSession ( zosmfProfile ) ;
243
+ }
244
+ catch ( error ) {
245
+ throw new Error ( "Failure occurred creating a zosmf session: " + error . message ) ;
246
+ }
218
247
}
219
248
220
249
private async createSshSession ( sshProfile : IProfile ) : Promise < SshSession > {
221
- return SshSession . createBasicSshSession ( sshProfile ) ;
250
+ try {
251
+ return SshSession . createBasicSshSession ( sshProfile ) ;
252
+ }
253
+ catch ( error ) {
254
+ throw new Error ( "Failure occurred creating an ssh session: " + error . message ) ;
255
+ }
256
+ }
257
+
258
+ private async createCicsSession ( cicsProfile : IProfile ) : Promise < AbstractSession > {
259
+ if ( cicsProfile === undefined ) {
260
+ return undefined ;
261
+ }
262
+
263
+ // At time of writing, the CicsSession object in the @zowe/cics project isn't
264
+ // accessible, so the following code is copied out of CicsSession.createBasicCicsSession().
265
+ try {
266
+ return new Session ( {
267
+ type : "basic" ,
268
+ hostname : cicsProfile . host ,
269
+ port : cicsProfile . port ,
270
+ user : cicsProfile . user ,
271
+ password : cicsProfile . password ,
272
+ basePath : cicsProfile . basePath ,
273
+ protocol : cicsProfile . protocol || "http" ,
274
+ rejectUnauthorized : cicsProfile . rejectUnauthorized
275
+ } ) ;
276
+ }
277
+ catch ( error ) {
278
+ throw new Error ( "Failure occurred creating a cics session: " + error . message ) ;
279
+ }
222
280
}
223
281
224
282
private async validateBundleDirExistsAndIsEmpty ( zosMFSession : AbstractSession ) {
@@ -282,16 +340,40 @@ export class BundlePusher {
282
340
this . startProgressBar ( ) ;
283
341
}
284
342
285
- private async deployBundle ( zosMFSession : AbstractSession , bd : BundleDeployer ) {
343
+ private async deployBundle ( zosMFSession : AbstractSession , bd : BundleDeployer , cicsSession : AbstractSession ) {
286
344
// End the current progress bar so that DEPLOY can create its own
287
345
this . updateStatus ( "Deploying bundle '" + this . params . arguments . name + "' to CICS" ) ;
288
346
this . endProgressBar ( ) ;
289
347
290
- await bd . deployBundle ( zosMFSession ) ;
348
+ let deployError : Error ;
349
+ let dfhdployOutput = "" ;
350
+ try {
351
+ await bd . deployBundle ( zosMFSession ) ;
352
+ }
353
+ catch ( error ) {
354
+ // temporarily ignore the error as we might want to generate additional resource
355
+ // specific diagnostics even if something went wrong.
356
+ deployError = error ;
357
+ }
358
+ dfhdployOutput = bd . getJobOutput ( ) ;
359
+
291
360
// Resume the current progress bar
292
361
this . endProgressBar ( ) ;
293
- this . updateStatus ( "Deploy complete" ) ;
362
+ if ( deployError === undefined ) {
363
+ this . updateStatus ( "Deploy complete" ) ;
364
+ }
365
+ else {
366
+ this . updateStatus ( "Deploy ended with errors" ) ;
367
+ }
294
368
this . startProgressBar ( ) ;
369
+
370
+ // Generate additional output for Node.js
371
+ await this . outputNodejsDiagnostics ( cicsSession , dfhdployOutput ) ;
372
+
373
+ // Now rethrow the original error, if there was one.
374
+ if ( deployError !== undefined ) {
375
+ throw deployError ;
376
+ }
295
377
}
296
378
297
379
private sshOutput ( data : string ) {
@@ -527,4 +609,87 @@ export class BundlePusher {
527
609
await this . runSingleNpmUninstall ( sshSession , remoteDirectory ) ;
528
610
}
529
611
}
612
+
613
+ private async outputNodejsDiagnostics ( cicsSession : AbstractSession , dfhdployOutput : string ) {
614
+ // Did the Bundle contents include a NODEJSAPP?
615
+ if ( dfhdployOutput . indexOf ( "http://www.ibm.com/xmlns/prod/cics/bundle/NODEJSAPP" ) > - 1 ) {
616
+ let diagnosticsIssued = false ;
617
+ try {
618
+ if ( cicsSession !== undefined ) {
619
+ // Attempt to gather additional Node.js specific information from CICS
620
+ this . updateStatus ( "Gathering Node.js diagnostics" ) ;
621
+ diagnosticsIssued = await this . gatherNodejsDiagnosticsFromCics ( cicsSession ) ;
622
+ }
623
+ }
624
+ catch ( diagnosticsError ) {
625
+ // Something went wrong generating diagnostic info. Don't trouble the user
626
+ // with what might be an exotic error message (e.g. hex dump of an entire HTML page),
627
+ // just log the failure.
628
+ if ( this . params . arguments . silent === undefined ) {
629
+ const logger = Logger . getAppLogger ( ) ;
630
+ logger . debug ( diagnosticsError . message ) ;
631
+ }
632
+ }
633
+
634
+ // If we don't have a cics profile or if we do but the diagnostics collection failed, issue a message.
635
+ if ( diagnosticsIssued === false ) {
636
+ const msg = "For further information on the state of your NODEJSAPP resources, consider running the following command:\n\n" +
637
+ "zowe cics get resource CICSNodejsapp --region-name " + this . params . arguments . scope +
638
+ " --criteria \"BUNDLE=" + this . params . arguments . name + "\" --cics-plex " + this . params . arguments . cicsplex + "\n" ;
639
+ this . issueMessage ( msg ) ;
640
+ }
641
+ }
642
+ }
643
+
644
+ private async gatherNodejsDiagnosticsFromCics ( cicsSession : AbstractSession ) : Promise < boolean > {
645
+ // Issue a CMCI get to the target CICSplex
646
+ try {
647
+ const data : IResourceParms = { name : "CICSNodejsapp" ,
648
+ criteria : "BUNDLE=" + this . params . arguments . name ,
649
+ regionName : this . params . arguments . scope ,
650
+ cicsPlex : this . params . arguments . cicsplex } ;
651
+ const cmciResponse = await getResource ( cicsSession , data ) ;
652
+ const outputRecord = cmciResponse . response . records . cicsnodejsapp ;
653
+ if ( outputRecord === undefined ) {
654
+ throw new Error ( "CICSNodejsapp output record not found." ) ;
655
+ }
656
+
657
+ // We may have an array of records if there was more than one NODEJSAPP in the bundle
658
+ if ( Array . isArray ( outputRecord ) ) {
659
+ for ( const record of outputRecord ) {
660
+ this . reportNODEJSAPPData ( record ) ;
661
+ }
662
+ }
663
+ else {
664
+ this . reportNODEJSAPPData ( outputRecord ) ;
665
+ }
666
+ }
667
+ catch ( error ) {
668
+ throw new Error ( "Failure collecting diagnostics for Bundle " + this . params . arguments . name + ": " + error . message ) ;
669
+ }
670
+
671
+ return true ;
672
+ }
673
+
674
+ private reportNODEJSAPPData ( outputRecord : any ) {
675
+ const name = outputRecord . name ;
676
+ const enablestatus = outputRecord . enablestatus ;
677
+ const pid = outputRecord . pid ;
678
+ const region = outputRecord . eyu_cicsname ;
679
+ let stdout = outputRecord . stdout ;
680
+ let stderr = outputRecord . stderr ;
681
+
682
+ if ( stdout === "" ) {
683
+ stdout = "<not available>" ;
684
+ }
685
+ if ( stderr === "" ) {
686
+ stderr = "<not available>" ;
687
+ }
688
+
689
+ const msg = "CICS NODEJSAPP resource '" + name + "' is in '" + enablestatus + "' state in region '" +
690
+ region + "' with process id '" + pid + "'.\n" +
691
+ " stdout: " + stdout + "\n" +
692
+ " stderr: " + stderr + "\n" ;
693
+ this . issueMessage ( msg ) ;
694
+ }
530
695
}
0 commit comments