@zowe/zos-jobs-for-zowe-sdk
Version:
Zowe SDK to interact with jobs on z/OS
493 lines • 25.3 kB
JavaScript
;
/*
* 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