Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce enhancements supporting the ZE FTP Copy feature #131

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to the z/OS FTP Plug-in for Zowe CLI will be documented in this file.

## Recent Changes

- Enhancement: Added APIs and corresponding CLI commands to support the Copy Dataset feature in the Zowe Explorer ZFTP extension.
- Enhancement: Added an API to allow dataset allocation while cloning attributes from another dataset (`allocateLikeDataSet`).
- Enhancement: Added a CLI command option to expose the Allocate-Like functionality (`zowe zftp alloc ds "new" --like "old"`).
- Enhancement: Added an API to allow copying dataset and dataset member contents (`copyDataSet`).
- Enhancement: Added a CLI command to expose the copying-dataset/member functionality (`zowe zftp copy ds "FROM(optional)" "TO(optional)"`).

## `2.1.2`

- Updated the `zos-node-accessor` package to 1.0.14 for technical currency.
Expand Down
7 changes: 7 additions & 0 deletions src/api/DataSetInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,12 @@ export interface IAllocateDataSetOption {
dcb?: string;
}

export interface ICopyDataSetOptions {
fromDsn: string;
toDsn: string;
progress?: IFTPProgressHandler;
replace?: boolean;
}

// When DataSetUtilsV2 for zos-node-accessor v2 is ready, alias DataSetUtilsV2 to DataSetUtils.
// export { DataSetUtils as DataSetUtils };
65 changes: 63 additions & 2 deletions src/api/DataSetUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
import * as fs from "fs";

import { IO, Logger } from "@zowe/imperative";
import { CoreUtils, TRANSFER_TYPE_ASCII, TRANSFER_TYPE_BINARY } from "./CoreUtils";
import { IAllocateDataSetOption, IDownloadDataSetOption, IUploadDataSetOption, TRACK } from "./DataSetInterface";
import { CoreUtils, TRANSFER_TYPE_ASCII, TRANSFER_TYPE_ASCII_RDW, TRANSFER_TYPE_BINARY, TRANSFER_TYPE_BINARY_RDW } from "./CoreUtils";
import { IAllocateDataSetOption, ICopyDataSetOptions, IDownloadDataSetOption, IUploadDataSetOption, TRACK } from "./DataSetInterface";
import { StreamUtils } from "./StreamUtils";
import { IFTPProgressHandler } from "./IFTPProgressHandler";
Fixed Show fixed Hide fixed

export class DataSetUtils {

Expand Down Expand Up @@ -147,6 +148,22 @@ export class DataSetUtils {
await connection.uploadDataset(content, "'" + dsn + "'", transferType, siteparm);
}

public static mapAllocationOptions(ds: any): any {
// supported options: https://github.com/IBM/zos-node-accessor/blob/1.0.x/lib/zosAccessor.js#LL122C68-L122C68
return {
volume: ds.volume, // Not supported by connection.allocateDataset
recfm: ds.recfm,
BLOCKSIze: ds.blksz, // Strange mapping
lrecl: ds.lrecl,
dsorg: ds.dsorg,
// BLocks: ds.??? // Strage mapping + Not returned by connection.listDataset
// CYlinders: ds.??? // Strage mapping + Not returned by connection.listDataset
// TRacks: ds.??? // Strage mapping + Not returned by connection.listDataset
// Directory: ds.??? // Strage mapping + Not returned by connection.listDataset
// PRImary: ds.??? // Strage mapping + Not returned by connection.listDataset
// SECondary: ds.??? // Strage mapping + Not returned by connection.listDataset
};
}
/**
* Allocate dataset.
*
Expand All @@ -159,6 +176,50 @@ export class DataSetUtils {
await connection.allocateDataset(dsn, option.dcb);
}

public static async allocateLikeDataSet(connection: any, dsn: string, like: string): Promise<any> {
const newDs = await connection.listDataset(dsn) ?? [];
if (newDs.length !== 0) {
this.log.debug("Dataset %s already exists", dsn);
return DataSetUtils.mapAllocationOptions(newDs);
}

this.log.debug("Allocate data set '%s' with similar attributes to '%s", dsn, like);
const files = await connection.listDataset(like);

this.log.debug("Found %d matching data sets", files.length);
const filteredFiles: any[] = files?.map((file: any) => CoreUtils.addLowerCaseKeysToObject(file)) ?? [];
if (filteredFiles.length === 0) {
throw "No datasets found: " + like;
}
const ds = filteredFiles.find((file: any) => file.dsname.toUpperCase() === like.toUpperCase());
const option = DataSetUtils.mapAllocationOptions(ds);

this.log.error(JSON.stringify(option));
await connection.allocateDataset(dsn, option);
return option;
}

public static async copyDataSet(connection: any, options: ICopyDataSetOptions): Promise<void> {
const files = await connection.listDataset(options.fromDsn);
const filteredFiles: any[] = files?.map((file: any) => CoreUtils.addLowerCaseKeysToObject(file)) ?? [];
if (filteredFiles.length === 0) {
throw new Error(`The dataset "${options.fromDsn}" doesn't exist.`);
}
const ds = filteredFiles.find((file: any) => file.dsname.toUpperCase() === options.fromDsn.toUpperCase());

// select the trasnfer type based on the record format
const transferType = ds.recfm[0] === "V" ? TRANSFER_TYPE_BINARY_RDW : ds.recfm[0] === "D" ? TRANSFER_TYPE_ASCII_RDW : TRANSFER_TYPE_BINARY;

// download the contents of the source dataset
const stream = await connection.getDataset(options.fromDsn, transferType, false);

// Make sure the new dataset is allocated
const option = await DataSetUtils.allocateLikeDataSet(connection, options.toDsn, options.fromDsn);

// upload the contents to the new dataset
await connection.uploadDataset(stream, "'"+options.toDsn+"'", transferType, option);
}

private static get log(): Logger {
return Logger.getAppLogger();
}
Expand Down
4 changes: 3 additions & 1 deletion src/cli/allocate/Allocate.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ const AllocateDefinition: ICommandDefinition = {
summary: "Allocate a sequential dataset or partitioned dataset with '--dcb \"PDSTYPE=PDS\"'",
description: "Allocate a sequential or partitioned dataset",
type: "group",
children: [AllocateDataSetDefinition],
children: [
AllocateDataSetDefinition,
],
passOn: [
{
property: "options",
Expand Down
17 changes: 12 additions & 5 deletions src/cli/allocate/data-set/DataSet.Handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ import { DataSetUtils } from "../../../api";
export default class AllocateDataSetHandler extends FTPBaseHandler {

public async processFTP(params: IFTPHandlerParams): Promise<void> {
const pResp = params.response;
const pArgs = params.arguments;
const options = {
dcb: params.arguments.dcb
dcb: pArgs.dcb
};
await DataSetUtils.allocateDataSet(params.connection, params.arguments.datasetName, options);

const successMsg = params.response.console.log("Allocated dataset %s successfully!", params.arguments.datasetName);
params.response.data.setMessage(successMsg);
let successMsg: string = "";
if (pArgs.like) {
await DataSetUtils.allocateLikeDataSet(params.connection, pArgs.datasetName, pArgs.like);
successMsg = pResp.console.log("Allocated dataset %s like %s successfully!", pArgs.datasetName, pArgs.like);
} else {
await DataSetUtils.allocateDataSet(params.connection, pArgs.datasetName, options);
successMsg = pResp.console.log("Allocated dataset %s successfully!", pArgs.datasetName);
}
pResp.data.setMessage(successMsg);
this.log.info(successMsg);
}
}
15 changes: 13 additions & 2 deletions src/cli/allocate/data-set/DataSet.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ export const AllocateDataSetDefinition: ICommandDefinition = {
{
description: "Allocate a sequential dataset \"IBMUSER.DATASET\"",
options: "\"IBMUSER.DATASET\""
}, {
},
{
description: "Allocate a partitioned dataset \"IBMUSER.DATASET\"",
options: "\"IBMUSER.DATASET\" --dcb \"PDSTYPE=PDS\""
},
{
description: "Allocate a dataset \"IBMUSER.NEW.DATASET\" " +
"with the same attributes as \"IBMUSER.ORIGINAL.DATASET\"",
options: "\"IBMUSER.NEW.DATASET\" --like \"IBMUSER.ORIGINAL.DATASET\""
}

],
positionals: [{
name: "datasetName",
Expand All @@ -42,6 +47,12 @@ export const AllocateDataSetDefinition: ICommandDefinition = {
"For the list of possible DCB parameters, " +
"visit https://github.com/IBM/zos-node-accessor/tree/1.0.x#allocate.",
type: "string"
},
{
name: "like", aliases: [],
description: "Dataset name to copy the attributes from.",
required: true,
type: "string"
}
],
profile:
Expand Down
36 changes: 36 additions & 0 deletions src/cli/copy/Copy.definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.
*
*/

import { ICommandDefinition } from "@zowe/imperative";
import { CopyDataSetDefinition } from "./data-set/DataSet.definition";
import { FTPConfig } from "../../api/FTPConfig";

const CopyDefinition: ICommandDefinition = {
name: "copy", aliases: ["cp"],
summary: "Copy datasets and dataset member content",
description: "Copy datasets and dataset member content",
type: "group",
children: [
CopyDataSetDefinition,
],
passOn: [
{
property: "options",
value: FTPConfig.FTP_CONNECTION_OPTIONS,
merge: true,
ignoreNodes: [
{type: "group"}
]
}
]
};

export = CopyDefinition;
34 changes: 34 additions & 0 deletions src/cli/copy/data-set/DataSet.Handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.
*
*/

import { ZosFilesMessages, ZosFilesUtils } from "@zowe/cli";
Fixed Show fixed Hide fixed
import { FTPBaseHandler } from "../../../FTPBase.Handler";
import { IFTPHandlerParams } from "../../../IFTPHandlerParams";
import { FTPProgressHandler } from "../../../FTPProgressHandler";
import { DataSetUtils, TRANSFER_TYPE_ASCII, TRANSFER_TYPE_ASCII_RDW, TRANSFER_TYPE_BINARY, TRANSFER_TYPE_BINARY_RDW } from "../../../api";
Fixed Show fixed Hide fixed

export default class DownloadDataSetHandler extends FTPBaseHandler {
public async processFTP(params: IFTPHandlerParams): Promise<void> {
const pResp = params.response;
const pArgs = params.arguments;
const progress = new FTPProgressHandler(params.response.progress, true);
await DataSetUtils.copyDataSet(params.connection, {
fromDsn: pArgs.fromDataSetName,
toDsn: pArgs.toDataSetName,
progress,
replace: pArgs.replace ?? false,
});

const successMsg = pResp.console.log("Copied dataset %s to %s successfully!", pArgs.fromDataSetName, pArgs.toDataSetName);
this.log.info(successMsg);
params.response.data.setMessage(successMsg);
}
}
49 changes: 49 additions & 0 deletions src/cli/copy/data-set/DataSet.definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.
*
*/

import { ICommandDefinition } from "@zowe/imperative";

export const CopyDataSetDefinition: ICommandDefinition = {
handler: __dirname + "/DataSet.Handler",
description: "Copy the contents of a z/OS dataset to another z/OS dataset",
type: "command",
name: "data-set", aliases: ["ds"],
summary: "Copy dataset or dataset member content",
examples: [
{
description: "Copy the sequential data set \"ibmuser.seq.dataset\" to \"ibmuser.another.seq.dataset\"",
options: "\"ibmuser.seq.dataset\" \"ibmuser.another.seq.dataset\""
},
],
positionals: [
{
name: "fromDataSetName",
description: "The data set (PDS member or physical sequential data set) which you would like to copy the contents from.",
type: "string",
required: true
},
{
name: "toDataSetName",
description: "The data set (PDS member or physical sequential data set) which you would like to copy the contents to.",
type: "string",
required: true
},
],
options: [
{
name: "replace",
aliases: ["rep"],
description: "Specify this option as true if you wish to replace like-named members in the target dataset",
type: "boolean"
}
],
profile: { optional: ["zftp"] },
};