UNPKG

@zowe/zos-jobs-for-zowe-sdk

Version:
493 lines 25.3 kB
"use strict"; /* * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * * Copyright Contributors to the Zowe Project. * */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SubmitJobs = void 0; const core_for_zowe_sdk_1 = require("@zowe/core-for-zowe-sdk"); const imperative_1 = require("@zowe/imperative"); const JobsConstants_1 = require("./JobsConstants"); const JobsMessages_1 = require("./JobsMessages"); const GetJobs_1 = require("./GetJobs"); const DownloadJobs_1 = require("./DownloadJobs"); const MonitorJobs_1 = require("./MonitorJobs"); /** * Class to handle submitting of z/OS batch jobs via z/OSMF * @export * @class SubmitJobs */ class SubmitJobs { /** * Submit a job that resides in a z/OS data set. * @static * @param {AbstractSession} session - z/OSMF connection info * @param {string} jobDataSet - job data set to be translated into parms object * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitJob(session, jobDataSet) { this.log.trace("submitJob called with data set %s", jobDataSet); return SubmitJobs.submitJobCommon(session, { jobDataSet }); } /** * Submit a job that resides in a USS File. * @static * @param {AbstractSession} session - z/OSMF connection info * @param {string} jobUSSFile - job USS File to be translated into parms object * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitUSSJob(session, jobUSSFile) { this.log.trace("submitJob called with USS file %s", jobUSSFile); return SubmitJobs.submitJobCommon(session, { jobUSSFile }); } /** * Submit a job that resides in a z/OS data set or USS file. * @static * @param {AbstractSession} session - z/OSMF connection info * @param {ISubmitJobParms} parms - parm object (see for details) * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitJobCommon(session, parms) { return __awaiter(this, void 0, void 0, function* () { this.log.trace("submitJobCommon called with parms %s", JSON.stringify(parms)); let extraHeaders = []; if (parms.jclSymbols) { extraHeaders = this.getSubstitutionHeaders(parms.jclSymbols); } let jobObj; if ("jobDataSet" in parms) { imperative_1.ImperativeExpect.keysToBeDefined(parms, ["jobDataSet"], "You must provide a data set containing JCL to submit in parms.jobDataSet"); this.log.debug("Submitting a job located in the data set '%s'", parms.jobDataSet); const fullyQualifiedDataset = "//'" + parms.jobDataSet + "'"; jobObj = { file: fullyQualifiedDataset }; } else if ("jobUSSFile" in parms) { imperative_1.ImperativeExpect.keysToBeDefined(parms, ["jobUSSFile"], "You must provide a USS file containing JCL to submit in parms.jobUSSFile"); this.log.debug("Submitting a job located in the USS file '%s'", parms.jobUSSFile); jobObj = { file: parms.jobUSSFile }; } else { throw new imperative_1.ImperativeError({ msg: "You must provide a data set or USS file containing JCL to submit" }); } return core_for_zowe_sdk_1.ZosmfRestClient.putExpectJSON(session, JobsConstants_1.JobsConstants.RESOURCE, [imperative_1.Headers.APPLICATION_JSON, ...extraHeaders], jobObj); }); } /** * Submit a string of JCL to run * @static * @param {AbstractSession} session - z/OSMF connection info * @param {string} jcl - string of JCL that you want to be submit * @param {string} internalReaderRecfm - record format of the jcl you want to submit. "F" (fixed) or "V" (variable) * @param {string} internalReaderLrecl - logical record length of the jcl you want to submit * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitJcl(session, jcl, internalReaderRecfm, internalReaderLrecl, internalReaderFileEncoding) { this.log.trace("submitJcl called with jcl of length %d. internalReaderRecfm %s internalReaderLrecl %s", jcl == null ? "no jcl!" : jcl.length, internalReaderRecfm, internalReaderLrecl); return SubmitJobs.submitJclCommon(session, { jcl, internalReaderRecfm, internalReaderLrecl, internalReaderFileEncoding }); } static submitJclString(session, jcl, parms) { return __awaiter(this, void 0, void 0, function* () { imperative_1.ImperativeExpect.toNotBeNullOrUndefined(jcl, JobsMessages_1.ZosJobsMessages.missingJcl.message); imperative_1.ImperativeExpect.toNotBeEqual(jcl, "", JobsMessages_1.ZosJobsMessages.missingJcl.message); const responseJobInfo = yield SubmitJobs.submitJclCommon(session, { jcl, jclSymbols: parms.jclSymbols, internalReaderFileEncoding: parms.internalReaderFileEncoding, internalReaderLrecl: parms.internalReaderLrecl, internalReaderRecfm: parms.internalReaderRecfm }); const response = this.checkSubmitOptions(session, parms, responseJobInfo); return response; }); } /** * Submit a JCL string to run * @static * @param {AbstractSession} session - z/OSMF connection info * @param {ISubmitJclParms} parms - parm object (see for details) * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitJclCommon(session, parms) { return __awaiter(this, void 0, void 0, function* () { this.log.trace("submitJclCommon called with parms %s", JSON.stringify(parms)); imperative_1.ImperativeExpect.keysToBeDefined(parms, ["jcl"], "You must provide a JCL string to submit. The 'jcl' field of the provided parameters was undefined. "); this.log.debug("Submitting JCL of length %d", parms.jcl.length); const headers = [imperative_1.Headers.TEXT_PLAIN_UTF8, core_for_zowe_sdk_1.ZosmfHeaders.X_IBM_INTRDR_MODE_TEXT]; if (parms.internalReaderLrecl) { this.log.debug("Custom internal reader logical record length (internalReaderLrecl) '%s' specified ", parms.internalReaderLrecl); headers.push({ "X-IBM-Intrdr-Lrecl": parms.internalReaderLrecl }); } else { // default to 80 record length headers.push(core_for_zowe_sdk_1.ZosmfHeaders.X_IBM_INTRDR_LRECL_80); } if (parms.internalReaderRecfm) { this.log.debug("Custom internal reader record format (internalReaderRecfm) '%s' specified ", parms.internalReaderRecfm); headers.push({ [core_for_zowe_sdk_1.ZosmfHeaders.X_IBM_INTRDR_RECFM]: parms.internalReaderRecfm }); } else { // default to fixed format records headers.push(core_for_zowe_sdk_1.ZosmfHeaders.X_IBM_INTRDR_RECFM_F); } if (parms.jclSymbols) { const extraHeaders = this.getSubstitutionHeaders(parms.jclSymbols); headers.push(...extraHeaders); } if (parms.internalReaderFileEncoding) { headers.push({ [core_for_zowe_sdk_1.ZosmfHeaders.X_IBM_INTRDR_FILE_ENCODING]: parms.internalReaderFileEncoding }); } return core_for_zowe_sdk_1.ZosmfRestClient.putExpectJSON(session, JobsConstants_1.JobsConstants.RESOURCE, headers, parms.jcl); }); } /** * Submit a JCL string to run * @static * @param {AbstractSession} session - z/OSMF connection info * @param {string} jcl - string of JCL that you want to be submit * @param {string} internalReaderRecfm - record format of the jcl you want to submit. "F" (fixed) or "V" (variable). * @param {string} internalReaderLrecl - logical record length of the jcl you want to submit * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitJclNotify(session, jcl, internalReaderRecfm, internalReaderLrecl, internalReaderFileEncoding) { return __awaiter(this, void 0, void 0, function* () { this.log.trace("submitJclNotiy called with jcl of length %s, internalReaderRecfm %s, internalReaderLrecl %s, internalReaderFileEncoding %s", jcl == null ? "no jcl!" : jcl.length, internalReaderRecfm, internalReaderLrecl, internalReaderFileEncoding); return SubmitJobs.submitJclNotifyCommon(session, { jcl, internalReaderRecfm, internalReaderLrecl, internalReaderFileEncoding }); }); } /** * Submit a job from a string of JCL and be notified whenever it reaches the default status on a default polling interval. * @static * @param {AbstractSession} session - z/OSMF connection info * @param {ISubmitJclNotifyParm} parms - parm object (see for details) * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitJclNotifyCommon(session, parms) { return __awaiter(this, void 0, void 0, function* () { this.log.trace("submitJclNotifyCommon called with parms %s", JSON.stringify(parms)); const job = yield SubmitJobs.submitJclCommon(session, parms); return SubmitJobs.submitNotifyCommon(session, job, parms.status, parms.watchDelay); }); } /** * Submit a job and be notified whenever it reaches the default status on a default polling interval. * @static * @param {AbstractSession} session - z/OSMF connection info * @param {string} jobDataSet - job data set to be translated into parms object with assumed defaults * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitJobNotify(session, jobDataSet) { return __awaiter(this, void 0, void 0, function* () { this.log.trace("submitJobNotify called with data set %s", jobDataSet); return SubmitJobs.submitJobNotifyCommon(session, { jobDataSet }); }); } /** * Submit a job and be notified whenever it reaches the default status on a default polling interval. * @static * @param {AbstractSession} session - z/OSMF connection info * @param {string} jobUSSFile - job USS file to be translated into parms object with assumed defaults * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitUSSJobNotify(session, jobUSSFile) { return __awaiter(this, void 0, void 0, function* () { this.log.trace("submitJobNotify called with data set %s", jobUSSFile); return SubmitJobs.submitJobNotifyCommon(session, { jobUSSFile }); }); } /** * Submit a job from a data set and be notified whenever it reaches a certain status. * If not status is specified, MonitorJobs.DEFAULT_STATUS is assumed. * The polling interval can also be optionally controlled via parms.watchDelay. * If not specified, the default polling is MonitorJobs.DEFAULT_WATCH_DELAY. * @static * @param {AbstractSession} session - z/OSMF connection info * @param {ISubmitJobNotifyParm} parms - parm object (see for details) * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitJobNotifyCommon(session, parms) { return __awaiter(this, void 0, void 0, function* () { this.log.trace("submitJobNotifyCommon called with parms %s", JSON.stringify(parms)); const job = yield SubmitJobs.submitJobCommon(session, parms); return SubmitJobs.submitNotifyCommon(session, job, parms.status, parms.watchDelay); }); } /** * Common method to handle job submit options * @public * @static * @param {AbstractSession} session - z/OSMF connection info * @param {ISubmitParms } parms - Submit options * @param {IJob} responseJobInfo - job document for a previously submitted job * @returns {Promise<IJob | ISpoolFile[]>} - Promise that resolves to an IJob or ISpoolFile[] * @memberof SubmitJobs */ static checkSubmitOptions(session, parms, responseJobInfo) { return __awaiter(this, void 0, void 0, function* () { if (parms.waitForActive) { const activeJob = yield MonitorJobs_1.MonitorJobs.waitForStatusCommon(session, { jobid: responseJobInfo.jobid, jobname: responseJobInfo.jobname, status: "ACTIVE" }); return activeJob; } //otherwise wait for output if (parms.directory) { if (parms.task != null) { parms.task.statusMessage = "Waiting for " + responseJobInfo.jobid + " to enter OUTPUT"; parms.task.percentComplete = imperative_1.TaskProgress.THIRTY_PERCENT; } const job = yield MonitorJobs_1.MonitorJobs.waitForJobOutputStatus(session, responseJobInfo); const downloadParms = { jobid: job.jobid, jobname: job.jobname, outDir: parms.directory }; if (parms.extension) { downloadParms.extension = imperative_1.IO.normalizeExtension(parms.extension); } if (parms.task != null) { parms.task.statusMessage = "Downloading spool content for " + job.jobid + (job.retcode == null ? "" : ", " + job.retcode); parms.task.percentComplete = imperative_1.TaskProgress.SEVENTY_PERCENT; } yield DownloadJobs_1.DownloadJobs.downloadAllSpoolContentCommon(session, downloadParms); return job; } else if (parms.viewAllSpoolContent || parms.waitForOutput) { if (parms.task != null) { parms.task.statusMessage = "Waiting for " + responseJobInfo.jobid + " to enter OUTPUT"; parms.task.percentComplete = imperative_1.TaskProgress.THIRTY_PERCENT; } const job = yield MonitorJobs_1.MonitorJobs.waitForJobOutputStatus(session, responseJobInfo); if (!parms.viewAllSpoolContent) { return job; } if (parms.task != null) { parms.task.statusMessage = "Retrieving spool content for " + job.jobid + (job.retcode == null ? "" : ", " + job.retcode); parms.task.percentComplete = imperative_1.TaskProgress.SEVENTY_PERCENT; } const spoolFiles = yield GetJobs_1.GetJobs.getSpoolFilesForJob(session, job); const arrOfSpoolFile = []; for (const file of spoolFiles) { const spoolContent = yield GetJobs_1.GetJobs.getSpoolContent(session, file); arrOfSpoolFile.push({ id: file.id, ddName: file.ddname, stepName: file.stepname, procName: file.procstep, data: spoolContent }); } return arrOfSpoolFile; } return responseJobInfo; }); } /** * Common method to watch for a job to reach a certain status whether the job was * submitted through raw JCL statement or through a data set containing JCL. * @private * @static * @param {AbstractSession} session - z/OSMF connection info * @param {IJob} job - job document for a previously submitted job * @param {JOB_STATUS } status - status that we want this job to reach before notifying * @param {number} watchDelay - delay / interval to poll * @returns {Promise<IJob>} - Promise that resolves to an IJob document with details about the submitted job * @memberof SubmitJobs */ static submitNotifyCommon(session, job, status, watchDelay) { return __awaiter(this, void 0, void 0, function* () { this.log.trace("submitNotiyCommon called with job %s, status %s, watchDelay %s", JSON.stringify(job), status, watchDelay); imperative_1.ImperativeExpect.keysToBeDefined(job, ["jobname", "jobid"], "The job object you provide must contain both 'jobname' and 'jobid'."); this.log.debug("Waiting to be notified of job completion from Monitor Jobs API for job %s (%s)", job.jobname, job.jobid); return MonitorJobs_1.MonitorJobs.waitForStatusCommon(session, { jobname: job.jobname, jobid: job.jobid, status, watchDelay }); }); } /** * Parse input string for JCL substitution * @param {string} symbols - JCL substitution symbols * @returns {IHeaderContent[]} headers - Headers to add to the request * @memberof SubmitJobs */ static getSubstitutionHeaders(symbols) { const headers = []; const blank = " "; const equals = "="; const maxSymLen = 8; let symStartInx = 0; moreSymLoop: while (symStartInx < symbols.length) { // skip all blanks at the start of a sym def while (symbols[symStartInx] === blank) { if (++symStartInx >= symbols.length) { break moreSymLoop; } } // navigate to the end of the symbol let symName = null; let symEndInx; for (symEndInx = symStartInx; symEndInx < symbols.length; symEndInx++) { if (symbols[symEndInx] === equals) { symName = symbols.substring(symStartInx, symEndInx); break; } } if (symName == null) { throw new imperative_1.ImperativeError({ msg: `No equals '${equals}' character was specified to define a symbol name.` }); } if (symName.length === 0) { throw new imperative_1.ImperativeError({ msg: `No symbol name specified before the equals '${equals}' character.` }); } if (symName.length > maxSymLen) { throw new imperative_1.ImperativeError({ msg: `The symbol name '${symName}' is too long. It must 1 to ${maxSymLen} characters.` }); } let valStartInx = ++symEndInx; if (valStartInx >= symbols.length) { throw new imperative_1.ImperativeError({ msg: `No value specified for symbol name '${symName}'.` }); } // is our value in quotes? let valEndChar = blank; if (symbols[valStartInx] === SubmitJobs.singleQuote) { // do we have an escaped quote (two in a row). if (++valStartInx >= symbols.length) { throw new imperative_1.ImperativeError({ msg: "The value for symbol '" + symName + "' is missing a terminating quote (" + SubmitJobs.singleQuote + ")." }); } if (symbols[valStartInx] === SubmitJobs.singleQuote) { // point to the first of the two quotes --valStartInx; } else { valEndChar = SubmitJobs.singleQuote; } } // find the end of the value let valEndInx; for (valEndInx = valStartInx; valEndInx < symbols.length; valEndInx++) { if (symbols[valEndInx] === valEndChar) { if (valEndChar === SubmitJobs.singleQuote) { // do we have an escaped quote (two in a row). if (valEndInx + 1 < symbols.length && symbols[valEndInx + 1] === SubmitJobs.singleQuote) { // keep looking for a terminating quote valEndInx++; continue; } } // place the next sym def into our array of headers. const header = SubmitJobs.formSubstitutionHeader(symName, symbols, valStartInx, valEndInx); headers.push(header); break; } } if (valEndInx >= symbols.length) { if (valEndChar === SubmitJobs.singleQuote) { throw new imperative_1.ImperativeError({ msg: "The value for symbol '" + symName + "' is missing a terminating quote (" + SubmitJobs.singleQuote + ")." }); } else { /* Since it is unlikely to have a trailing blank at the end of the * last symbol value, just accept all remaining characters in the * argument as the value for the last symbol. */ const header = SubmitJobs.formSubstitutionHeader(symName, symbols, valStartInx, symbols.length); headers.push(header); } } // start the search for our next symbol definition symStartInx = ++valEndInx; } let logMsg = "Formed the following JCL symbol headers:\n"; headers.forEach((nextHeader) => { for (const key in nextHeader) { if (Object.prototype.hasOwnProperty.call(nextHeader, key)) { logMsg += " " + key + " = " + nextHeader[key] + "\n"; } } }); this.log.debug(logMsg); return headers; } /** * Form a header used for JCL symbol substitution * * @param {string} symName * The name of the JCL substitution symbol * * @param {string} symDefs * The CLI argument that contains all of the JCL substitution symbol definitions * * @param {string} valStartInx * Index into symDefs to the start of the value for symName. * * @param {string} valEndInx * Index into symDefs that is one past the end of the value for symName. * * @returns {IHeaderContent} * Header to add to our set of headers * @memberof SubmitJobs */ static formSubstitutionHeader(symName, symDefs, valStartInx, valEndInx) { // now that we identified the value, reduce occurrences of two quotes to one. const twoQuoteRegex = new RegExp(SubmitJobs.singleQuote + SubmitJobs.singleQuote, "g"); let symVal = symDefs.substring(valStartInx, valEndInx); symVal = symVal.replace(twoQuoteRegex, SubmitJobs.singleQuote); // construct the required header const key = core_for_zowe_sdk_1.ZosmfHeaders.X_IBM_JCL_SYMBOL_PARTIAL + symName.toUpperCase(); return { [key]: symVal }; } /** * Getter for Zowe logger * @returns {Logger} */ static get log() { return imperative_1.Logger.getAppLogger(); } } exports.SubmitJobs = SubmitJobs; // used to delimit a value in a JCL symbol definition SubmitJobs.singleQuote = "'"; //# sourceMappingURL=SubmitJobs.js.map