@zowe/zos-files-for-zowe-sdk
Version:
Zowe SDK to interact with files and data sets on z/OS
500 lines • 27 kB
JavaScript
"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.Copy = void 0;
const imperative_1 = require("@zowe/imperative");
const path_1 = require("path");
const fs = require("fs");
const create_1 = require("../create");
const get_1 = require("../get");
const upload_1 = require("../upload");
const list_1 = require("../list");
const core_for_zowe_sdk_1 = require("@zowe/core-for-zowe-sdk");
const ZosFiles_constants_1 = require("../../constants/ZosFiles.constants");
const ZosFiles_messages_1 = require("../../constants/ZosFiles.messages");
const download_1 = require("../download");
const ZosFilesUtils_1 = require("../../utils/ZosFilesUtils");
const os_1 = require("os");
const path = require("path");
const util = require("util");
const delete_1 = require("../delete");
/**
* This class holds helper functions that are used to copy the contents of datasets through the
* z/OSMF APIs.
*/
class Copy {
/**
* Copy the contents of a dataset
*
* @param {AbstractSession} session - z/OSMF connection info
* @param {IDataSet} toDataSet - The data set to copy to
* @param {IDataSetOptions} options - Options
*
* @returns {Promise<IZosFilesResponse>} A response indicating the status of the copying
*
* @throws {ImperativeError} Data set name must be specified as a non-empty string
* @throws {Error} When the {@link ZosmfRestClient} throws an error
*
* @see https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.1.0/com.ibm.zos.v2r1.izua700/IZUHPINFO_API_PutDataSetMemberUtilities.htm
*/
static dataSet(session_1, _a, options_1) {
return __awaiter(this, arguments, void 0, function* (session, { dsn: toDataSetName, member: toMemberName }, options) {
imperative_1.ImperativeExpect.toBeDefinedAndNonBlank(options["from-dataset"].dsn, "fromDataSetName");
imperative_1.ImperativeExpect.toBeDefinedAndNonBlank(toDataSetName, "toDataSetName");
const safeReplace = options.safeReplace;
const overwriteMembers = options.replace;
const task = {
percentComplete: 0,
statusMessage: "Copying data set",
stageName: imperative_1.TaskStage.IN_PROGRESS
};
const sourceDataSetExists = yield this.dataSetExists(session, options["from-dataset"].dsn);
if (!sourceDataSetExists) {
return {
success: false,
commandResponse: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedAbortedNoTargetDS.message
};
}
if (options["from-dataset"].dsn === toDataSetName && toMemberName === options["from-dataset"].member) {
return {
success: false,
commandResponse: ZosFiles_messages_1.ZosFilesMessages.identicalDataSets.message
};
}
const targetDataSetExists = yield this.dataSetExists(session, toDataSetName);
const overwriteDataset = options.overwrite;
if (overwriteDataset) {
yield delete_1.Delete.dataSet(session, toDataSetName);
}
const newDataSet = !targetDataSetExists || overwriteDataset;
if (newDataSet) {
yield create_1.Create.dataSetLike(session, toDataSetName, options["from-dataset"].dsn);
}
else if (safeReplace) {
if (options.promptFn != null) {
const userResponse = yield options.promptFn(toDataSetName);
if (!userResponse) {
throw new imperative_1.ImperativeError({ msg: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedAborted.message });
}
else if (options.progress && options.progress.startBar) {
options.progress.startBar({ task });
}
}
}
if (options.progress && options.progress.endBar) {
options.progress.endBar();
}
if (!toMemberName && !options["from-dataset"].member) {
const sourceIsPds = yield this.isPDS(session, options["from-dataset"].dsn);
const targetIsPds = yield this.isPDS(session, toDataSetName);
if (sourceIsPds && targetIsPds) {
const sourceResponse = yield list_1.List.allMembers(session, options["from-dataset"].dsn);
const sourceMemberList = sourceResponse.apiResponse.items.map((item) => item.member);
const hasIdenticalMemberNames = yield this.hasIdenticalMemberNames(session, sourceMemberList, toDataSetName);
if (!safeReplace && hasIdenticalMemberNames && !overwriteMembers) {
const userResponse = yield options.promptForIdenticalNamedMembers();
if (!userResponse) {
throw new imperative_1.ImperativeError({ msg: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedAborted.message });
}
else if (options.progress && options.progress.startBar) {
options.progress.startBar({ task });
}
}
if (options.progress) {
if (options.progress.endBar) {
options.progress.endBar();
}
if (options.progress.startBar) {
options.progress.startBar({ task });
}
}
const response = yield this.copyPDS(session, options["from-dataset"].dsn, toDataSetName, task, sourceMemberList);
if (options.progress && options.progress.endBar) {
options.progress.endBar();
}
return {
success: true,
commandResponse: newDataSet && !overwriteDataset
? util.format(ZosFiles_messages_1.ZosFilesMessages.dataSetCopiedIntoNew.message, toDataSetName)
: response.commandResponse
};
}
}
const endpoint = path_1.posix.join(ZosFiles_constants_1.ZosFilesConstants.RESOURCE, ZosFiles_constants_1.ZosFilesConstants.RES_DS_FILES, encodeURIComponent(toMemberName == null ? toDataSetName : `${toDataSetName}(${toMemberName})`));
imperative_1.Logger.getAppLogger().debug(`Endpoint: ${endpoint}`);
const payload = Object.assign({ "request": "copy", "from-dataset": {
dsn: options["from-dataset"].dsn,
member: options["from-dataset"].member
} }, options);
delete payload.fromDataSet;
const reqHeaders = [
imperative_1.Headers.APPLICATION_JSON,
{ [imperative_1.Headers.CONTENT_LENGTH]: JSON.stringify(payload).length.toString() },
core_for_zowe_sdk_1.ZosmfHeaders.ACCEPT_ENCODING
];
if (options.responseTimeout != null) {
reqHeaders.push({ [core_for_zowe_sdk_1.ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString() });
}
try {
yield core_for_zowe_sdk_1.ZosmfRestClient.putExpectString(session, endpoint, reqHeaders, payload);
return {
success: true,
commandResponse: newDataSet
? util.format(ZosFiles_messages_1.ZosFilesMessages.dataSetCopiedIntoNew.message, toDataSetName)
: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedSuccessfully.message
};
}
catch (error) {
imperative_1.Logger.getAppLogger().error(error);
throw error;
}
});
}
/**
* Function that checks if a dataset is type PDS
**/
static isPDS(session, dataSetName) {
return __awaiter(this, void 0, void 0, function* () {
try {
const response = yield list_1.List.dataSet(session, dataSetName, { attributes: true });
const dsorg = response.apiResponse.items[0].dsorg;
return dsorg.startsWith("PO");
}
catch (error) {
imperative_1.Logger.getAppLogger().error(error);
throw error;
}
});
}
/**
* Function that checks if the data set exists
**/
static dataSetExists(session, dataSetName) {
return __awaiter(this, void 0, void 0, function* () {
let dsnameIndex;
const dataSetList = yield list_1.List.dataSet(session, dataSetName, { start: dataSetName });
if (dataSetList.apiResponse != null && dataSetList.apiResponse.returnedRows != null && dataSetList.apiResponse.items != null) {
dsnameIndex = dataSetList.apiResponse.returnedRows === 0 ? -1 :
dataSetList.apiResponse.items.findIndex((ds) => ds.dsname.toUpperCase() === dataSetName.toUpperCase());
}
return dsnameIndex !== -1;
});
}
/**
* Function that checks if source and target data sets have identical member names
*/
static hasIdenticalMemberNames(session, sourceMemberList, toPds) {
return __awaiter(this, void 0, void 0, function* () {
const targetResponse = yield list_1.List.allMembers(session, toPds);
const targetMemberList = targetResponse.apiResponse.items.map((item) => item.member);
return sourceMemberList.some((mem) => targetMemberList.includes(mem));
});
}
/**
* Copy the members of a Partitioned dataset into another Partitioned dataset
*
* @param {AbstractSession} session - z/OSMF connection info
* @param {IDataSet} toDataSet - The data set to copy to
* @param {IDataSetOptions} options - Options
*
* @returns {Promise<IZosFilesResponse>} A response indicating the status of the copying
*
* @throws {ImperativeError} Data set name must be specified as a non-empty string
* @throws {Error} When the {@link ZosmfRestClient} throws an error
*
* @see https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.1.0/com.ibm.zos.v2r1.izua700/IZUHPINFO_API_PutDataSetMemberUtilities.htm
*/
static copyPDS(session, fromPds, toPds, task, sourceMemberList) {
return __awaiter(this, void 0, void 0, function* () {
try {
if ((sourceMemberList === null || sourceMemberList === void 0 ? void 0 : sourceMemberList.length) === 0) {
return {
success: true,
commandResponse: `Source dataset (${fromPds}) - ` + ZosFiles_messages_1.ZosFilesMessages.noMembersFound.message
};
}
const downloadDir = path.join((0, os_1.tmpdir)(), fromPds);
yield download_1.Download.allMembers(session, fromPds, { directory: downloadDir });
const uploadFileList = ZosFilesUtils_1.ZosFilesUtils.getFileListFromPath(downloadDir);
const truncatedMembers = [];
let membersInitiated = 0;
for (const file of uploadFileList) {
const memName = ZosFilesUtils_1.ZosFilesUtils.generateMemberName(file);
const uploadingDsn = `${toPds}(${memName})`;
if (task != null) {
const LAST_FIFTEEN_CHARS = -16;
const abbreviatedFile = file.slice(LAST_FIFTEEN_CHARS);
task.statusMessage = "Copying... " + abbreviatedFile;
task.percentComplete = Math.floor(imperative_1.TaskProgress.ONE_HUNDRED_PERCENT *
(membersInitiated / uploadFileList.length));
membersInitiated++;
}
try {
const uploadStream = imperative_1.IO.createReadStream(file);
yield upload_1.Upload.streamToDataSet(session, uploadStream, uploadingDsn);
}
catch (error) {
if (error.message && error.message.includes("Truncation of a record occurred during an I/O operation")) {
truncatedMembers.push(memName);
}
else {
imperative_1.Logger.getAppLogger().error(error);
}
continue;
}
}
const truncatedMembersFile = path.join((0, os_1.tmpdir)(), 'truncatedMembers.txt');
if (truncatedMembers.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
const firstTenMembers = truncatedMembers.slice(0, 10);
fs.writeFileSync(truncatedMembersFile, truncatedMembers.join('\n'), { flag: 'w' });
const numMembers = truncatedMembers.length - firstTenMembers.length;
return {
success: true,
commandResponse: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedSuccessfully.message + " " +
ZosFiles_messages_1.ZosFilesMessages.membersContentTruncated.message + "\n" +
firstTenMembers.join('\n') +
(numMembers > 0 ? `\n... and ${numMembers} more` +
util.format(ZosFiles_messages_1.ZosFilesMessages.viewMembersListfile.message, truncatedMembersFile) : '')
};
}
fs.rmSync(downloadDir, { recursive: true });
fs.rmSync(truncatedMembersFile, { force: true });
return {
success: true,
commandResponse: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedSuccessfully.message
};
}
catch (error) {
imperative_1.Logger.getAppLogger().error(error);
throw error;
}
});
}
/**
* Copy the contents of a dataset from one LPAR to another LPAR
*
* @param {AbstractSession} sourceSession - Source z/OSMF connection info
* @param {IDataSet} toDataSet - The data set to copy to
* @param {ICrossLparCopyDatasetOptions} targetOptions - Options for target file and connection
* @param {IGetOptions} sourceOptions - Options for source file
* @param {IDataSetOptions} options - Common options
* @param {IHandlerResponseConsoleApi} console - Command console object
*
* @returns {Promise<IZosFilesResponse>} A response indicating the status of the copying
*o
* @throws {ImperativeError} Data set name must be specified as a non-empty string
* @throws {Error} When the {@link ZosmfRestClient} throws an error
*
*/
static dataSetCrossLPAR(sourceSession_1, _a, options_1, sourceOptions_1, targetSession_1) {
return __awaiter(this, arguments, void 0, function* (sourceSession, { dsn: toDataSetName, member: toMemberName }, options, sourceOptions, targetSession) {
imperative_1.ImperativeExpect.toBeDefinedAndNonBlank(options["from-dataset"].dsn, "fromDataSetName");
imperative_1.ImperativeExpect.toBeDefinedAndNonBlank(toDataSetName, "toDataSetName");
try {
let sourceDataset = options["from-dataset"].dsn;
const sourceMember = options["from-dataset"].member;
let sourceDataSetObj;
let targetDataset = toDataSetName;
const targetMember = toMemberName;
let targetDataSetObj;
let targetFound = false;
let targetMemberFound = false;
let overwriteTarget = options.replace;
/**
* Does the source dataset exist?
*/
const SourceDsList = yield list_1.List.dataSet(sourceSession, sourceDataset, {
attributes: true, maxLength: 1,
start: sourceDataset,
recall: "wait"
});
if (SourceDsList.apiResponse != null &&
SourceDsList.apiResponse.returnedRows != null
&& SourceDsList.apiResponse.items != null) {
// Look for the index of the data set in the response from the List API
const dsnameIndex = SourceDsList.apiResponse.returnedRows === 0 ? -1 :
SourceDsList.apiResponse.items.findIndex((ds) => ds.dsname.toUpperCase() === sourceDataset.toUpperCase());
if (dsnameIndex !== -1) {
// If dsnameIndex === -1, it means we could not find the given data set.
// We will attempt the upload anyways so that we can forward/throw the proper error from z/OS MF
sourceDataSetObj = SourceDsList.apiResponse.items[dsnameIndex];
}
else {
throw new imperative_1.ImperativeError({ msg: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedAbortedNoTargetDS.message });
}
/*
* If the source is a PDS and no member was specified then abort the copy.
*/
if (sourceDataSetObj.dsorg.startsWith("PO") && sourceMember == undefined) {
throw new imperative_1.ImperativeError({ msg: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedAbortedNoPDS.message });
}
}
const task2 = {
percentComplete: 0,
statusMessage: "Retrieving data set contents",
stageName: imperative_1.TaskStage.IN_PROGRESS
};
/**
* Get the dataset and store it in a buffer
*
* We want to download and upload in binary, as this command is copying from z/OS to z/OS system.
* We do not want to do any sort of data conversion.
*/
if (sourceMember != undefined) {
sourceDataset = sourceDataset + "(" + sourceMember + ")";
}
const dsContentBuf = yield get_1.Get.dataSet(sourceSession, sourceDataset, { binary: true,
volume: sourceOptions.volume,
responseTimeout: options.responseTimeout,
task: task2
});
/**
* Does the target dataset exist?
*/
const TargetDsList = yield list_1.List.dataSet(targetSession, targetDataset, { attributes: true, maxLength: 1, start: targetDataset, recall: "wait" });
if (TargetDsList.apiResponse != null && TargetDsList.apiResponse.returnedRows != null && TargetDsList.apiResponse.items != null) {
// Look for the index of the data set in the response from the List API
const dsnameIndex = TargetDsList.apiResponse.returnedRows === 0 ? -1 :
TargetDsList.apiResponse.items.findIndex((ds) => ds.dsname.toUpperCase() === targetDataset.toUpperCase());
if (dsnameIndex !== -1) {
// If dsnameIndex === -1, it means we could not find the given data set.
// We will attempt the upload anyways so that we can forward/throw the proper error from z/OS MF
targetDataSetObj = TargetDsList.apiResponse.items[dsnameIndex];
targetFound = true;
if (targetDataSetObj.dsorg.startsWith("PO") && targetMember == undefined) {
throw new imperative_1.ImperativeError({ msg: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedAbortedTargetNotPDSMember.message });
}
}
}
/**
* If this is a PDS and it exists, verify if the member also exists.
*/
if (targetMember != undefined && targetFound == true) {
const TargetMemberList = yield list_1.List.allMembers(targetSession, targetDataset, { attributes: true, maxLength: 1, recall: "wait", start: targetMember });
if (TargetMemberList.apiResponse.returnedRows > 0) {
targetMemberFound = true;
}
}
/**
* Check to see if the target dataset exists and if the attributes match the source
*/
if (targetFound == false) {
/**
* Create the target dataset if it does not exist based on the source dataset values
*/
const createOptions = Copy.generateDatasetOptions(options, sourceDataSetObj);
/*
* If this is a PDS but the target is the sequential dataset and does not exist,
* create a new sequential dataset with the same parameters as the original PDS.
*/
if (createOptions.dsorg.startsWith("PO") && targetMember == undefined) {
createOptions.dsorg = "PS";
createOptions.dirblk = 0;
}
else if (targetMember != undefined && !createOptions.dsorg.startsWith("PO")) {
createOptions.dsorg = "PO";
createOptions.dirblk = 1;
}
yield create_1.Create.dataSet(targetSession, 2 /* CreateDataSetTypeEnum.DATA_SET_CLASSIC */, targetDataset, createOptions);
}
else {
if (overwriteTarget == undefined && targetMember == undefined) {
if (options.promptFn != null) {
overwriteTarget = yield options.promptFn(targetDataset);
}
}
else {
if (overwriteTarget == undefined && targetMemberFound == true) {
if (options.promptFn != null) {
overwriteTarget = yield options.promptFn(targetDataset + "(" + targetMember + ")");
}
}
}
}
/*
* Don't overwrite an existing dataset or member if overwrite is false
*/
if (overwriteTarget || !targetFound ||
targetMember != undefined && !targetMemberFound) {
/**
* Upload the source data to the target dataset
*/
if (targetMember != undefined) {
targetDataset = targetDataset + "(" + targetMember + ")";
}
yield upload_1.Upload.bufferToDataSet(targetSession, dsContentBuf, targetDataset, { binary: true
});
}
else {
throw new imperative_1.ImperativeError({ msg: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedAborted.message });
}
return {
success: true,
commandResponse: ZosFiles_messages_1.ZosFilesMessages.datasetCopiedSuccessfully.message
};
}
catch (error) {
imperative_1.Logger.getAppLogger().error(error);
throw error;
}
});
}
/**
* Private function to convert the dataset options from the format returned by the Get() call in to the format used by the Create() call
*/
static generateDatasetOptions(targetOptions, dsInfo) {
return JSON.parse(JSON.stringify({
alcunit: Copy.convertAlcTozOSMF(dsInfo.spacu),
dsorg: dsInfo.dsorg,
volser: targetOptions.targetVolser,
primary: parseInt(dsInfo.sizex),
secondary: parseInt(dsInfo.extx),
recfm: dsInfo.recfm,
blksize: parseInt(dsInfo.blksz),
lrecl: parseInt(dsInfo.lrecl),
storclass: targetOptions.targetStorageClass,
mgntclass: targetOptions.targetManagementClass,
dataclass: targetOptions.targetDataClass,
dirblk: parseInt(dsInfo.dsorg.startsWith("PO") ? "10" : "0")
}));
}
/**
* Converts the ALC value from the format returned by the Get() call to the format used by the Create() call.
* @param {string} getValue - The ALC value from the Get() call.
* @returns {string} - The ALC value in the format used by the Create() call.
*/
static convertAlcTozOSMF(getValue) {
/**
* Create dataset only accepts tracks or cylinders as allocation units.
* When the get() call retreives the dataset info, it will convert size
* allocations of the other unit types in to tracks. So we will always
* allocate the new target in tracks.
*/
const alcMap = {
"TRACKS": "TRK",
"CYLINDERS": "CYL"
};
return alcMap[getValue.toUpperCase()] || "TRK";
}
}
exports.Copy = Copy;
//# sourceMappingURL=Copy.js.map