A partitioned data set, or PDS for short, is a data set containing multiple members. This can be thought of as being similar to having files in a directory. When working on z/OS, it is often necessary to be able to read, write, or execute files contained in a PDS. The following video will demonstrate how you can use
IBM Open Enterprise SDK for Node.js to manage files in a PDS using Node.js by leveraging the
zoau node module:
In the above video, we create a web application that can edit, build, and run C source code residing in a PDS, and can be used by developers who are unfamiliar with z/OS.
We start by creating a project directory:
$ mkdir dpp-node-zoau-demo
$ cd dpp-node-zoau-demo
We then initialize our working directory as a new "npm package", and install the
express and
zoau node modules as dependencies:
$ npm init --yes
$ npm install express
$ npm install zoau
This will create a
package.json
file which describes our application, and contains information about our application's dependencies.
We'll then create
server.js
, which leverages the
express node module to set up a basic web server:
const path = require('path');
const express = require('express');
const routes = require('./routes');
const app = express();
const middlewares = [
// Serves static .html, .js, .css file, etc.
express.static(path.join(__dirname, 'public')),
// Parse requests containing json.
express.json(),
// Parse requests containing urlencoded bodies.
express.urlencoded({ extended: true }),
];
app.use(middlewares);
app.use('/', routes);
// Page Not Found handler.
app.use((req, res, next) => {
res.status(404).json('Sorry, page not found!');
});
// Server Error handler.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json(err.message);
});
// Using port 3000 for this example; can be changed to a port of your choice.
app.listen(3000, () => {
console.log(`App running at http://localhost:3000`);
});
We'll also need to create a
routes.js
, where we will write the code for our web application API end points:
const express = require('express');
const router = express.Router();
const zoau = require('zoau');
module.exports = router;
To create an end point that can list all the members within a PDS, we'll add the following code to routes.js:
// List members of a PDS.
router.get('/api/:pds', (req, res, next) => {
zoau.datasets.list_members(req.params.pds)
.then(members => res.json(members))
.catch(err => next(err));
});
This can be performed by sending a HTTP GET request to
http://localhost:3000/api/<name of pds>
.
To create an end point that can read the contents of a PDS member, we'll add the following code to
routes.js
:
// Get content of a member.
router.get('/api/:pds/:member', (req, res, next) => {
zoau.datasets.read(`${req.params.pds}(${req.params.member})`)
.then(content => res.json(content))
.catch(err => next(err));
});
This can be performed by sending a HTTP GET request to
http://localhost:3000/api/<name of pds>/<name of member>
.
To create an end point that can write to a PDS member, we'll add the following code to
routes.js
:
// Write to the member of a PDS.
router.put('/api/:pds/:member', (req, res, next) => {
let dataset = `${req.params.pds}(${req.params.member})`;
let content = req.body.content;
if (content === undefined || content === null) {
return next(new Error('Content must be specified'));
}
// Correctly format newlines, whitespace, etc.
content = content.replace(/(\r\n)/gm, "\n");
content = content.replace(/\s+\n/g, '\n');
content = content.replace(/\\/g, "\\\\");
// Write content to PDS member.
zoau.datasets.write(dataset, content)
.then(rc => res.json(rc))
.catch(err => next(err));
});
This can be performed by sending a HTTP PUT request to
http://localhost:3000/api/<name of pds>/<name of member>
with the data to write to the member specified in the request body.
Finally, to build and run the source code contained within a PDS member, we'll add the following code to
routes.js
:
const util = require('util');
const exec = util.promisify(require('child_process').exec);
// Build and run the member of a PDS.
router.post('/api/:pds/:member', (req, res) => {
let dataset = `${req.params.pds}(${req.params.member})`;
// Random binary name to prevent naming conflict.
let binary = `demo_${Math.random().toString(36).substring(7)}`;
// Compile and run.
exec(`cd /tmp/ && xlc -+ -o ${binary} "//'${dataset}'" && ./${binary}`)
.then(({ stdout, stderr }) => res.json(stdout + stderr))
.catch(err => res.json(err.message));
});
This can be performed by sending a HTTP POST request to
http://localhost:3000/api/<name of pds>/<name of member>
.
Putting it all together, our
routes.js
now contains the following:
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const express = require('express');
const router = express.Router();
const zoau = require('zoau');
// REST API
// List members of a PDS.
router.get('/api/:pds', (req, res, next) => {
zoau.datasets.list_members(req.params.pds)
.then(members => res.json(members))
.catch(err => next(err));
});
// Get content of a member.
router.get('/api/:pds/:member', (req, res, next) => {
zoau.datasets.read(`${req.params.pds}(${req.params.member})`)
.then(content => res.json(content))
.catch(err => next(err));
});
// Write to the member of a PDS.
router.put('/api/:pds/:member', (req, res, next) => {
let dataset = `${req.params.pds}(${req.params.member})`;
let content = req.body.content;
if (content === undefined || content === null) {
return next(new Error('Content must be specified'));
}
// Correctly format newlines, whitespace, etc.
content = content.replace(/(\r\n)/gm, "\n");
content = content.replace(/\s+\n/g, '\n');
content = content.replace(/\\/g, "\\\\");
// Write content to PDS member.
zoau.datasets.write(dataset, content)
.then(rc => res.json(rc))
.catch(err => next(err));
});
// Build and run the member of a PDS.
router.post('/api/:pds/:member', (req, res) => {
let dataset = `${req.params.pds}(${req.params.member})`;
// Random binary name to prevent naming conflict.
let binary = `demo_${Math.random().toString(36).substring(7)}`;
// Compile and run.
exec(`cd /tmp/ && xlc -+ -o ${binary} "//'${dataset}'" && ./${binary}`)
.then(({ stdout, stderr }) => res.json(stdout + stderr))
.catch(err => res.json(err.message));
});
module.exports = router;
Next, we will need a user interface that will make requests to the API end points. There are many approaches to creating a user interface. One possible approach is to use HTML and JavaScript to create a web UI that can be accessed at
http://localhost:3000
.
We'll need to create a directory for our web UI:
$ mkdir public
$ cd public
We'll create
index.html
which contains the HTML code for our web UI:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Managing files in a PDS using Node.js with zoau</title>
<!-- CSS. -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
</head>
<body>
<div class="container-sm" style="max-width: 540px;">
<div class=" g-3 mb-3">
<div class="row">
<div class="col-12 text-center"><h2>Managing files in a PDS using Node.js with zoau</h2></div>
</div>
<div id="results" class="row" style="display: none;">
<div class="col-12"><h4>Results:</h4></div>
<div class="col-12">
<pre id="output" style="border: 5px solid grey;"></pre>
</div>
</div>
</div>
<form class="row g-3 mb-3 bg-light border">
<div class="col-12">
<label for="pds" class="form-label">PDS</label>
<input type="text" name="pds" id="pds" value="DPP.CSRC" class="form-control">
</div>
<div class="col-12 d-grid gap-2">
<button type="button" onclick="listMembers()" class="btn btn-primary mb-3">List PDS Members</button>
</div>
</form>
<form class="row g-3 mb-3 bg-light border">
<div class="col-12">
<label for="members" class="form-label">Members</label>
<select name="members" id="members" class="form-select"></select>
</div>
<div class="col-12 d-grid gap-2">
<button type="button" onclick="getContent()" class="btn btn-primary mb-3">Read Member Content</button>
</div>
</form>
<form class="row g-3 mb-3 bg-light border">
<div class="col-12">
<label for="content" id="content-label" class="form-label">Content</label>
<textarea name="content" id="content" rows="20" class="form-control"></textarea>
</div>
<div class="col-12 d-grid gap-2">
<button type="button" onclick="writeContent()" class="btn btn-primary mb-3">Save</button>
<button type="button" onclick="buildAndRun()" class="btn btn-success mb-3">Build and Run</button>
</div>
</form>
</div>
<!-- Scripts. -->
<script src="script.js" type="text/javascript"></script>
</body>
</html>
We'll also create
script.js
which contains the JavaScript code to make requests to our API end points:
// List members of a PDS.
function listMembers() {
var pds = document.getElementById('pds').value;
fetch(`/api/${pds}`)
.then(response => response.json())
.then(members => {
var select = document.getElementById('members');
// Clear existing options.
for (var i = select.options.length - 1; i >= 0; i--) {
select.remove(i);
}
// Populate with new options.
for (member of members){
var option = document.createElement('option');
option.value = member;
option.innerHTML = member;
select.appendChild(option);
}
printOutput('Success');
})
.catch(err => printOutput(err));
}
// Get content of a member.
function getContent() {
var pds = document.getElementById('pds').value;
var member = document.getElementById('members').value;
fetch(`/api/${pds}/${member}`)
.then(response => response.json())
.then(content => {
document.getElementById('content-label').innerText = `${pds}(${member})`;
document.getElementById('content').value = content;
printOutput('Success');
})
.catch(err => printOutput(err));
}
// Write to the member of a PDS.
function writeContent() {
var pds = document.getElementById('pds').value;
var member = document.getElementById('members').value;
var content = document.getElementById('content').value;
fetch(`/api/${pds}/${member}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ content })
})
.then(response => response.json())
.then(rc => printOutput(`Return Code: ${rc}`))
.catch(err => printOutput(err));
}
// Build and run the member of a PDS.
function buildAndRun() {
var pds = document.getElementById('pds').value;
var member = document.getElementById('members').value;
fetch(`/api/${pds}/${member}`, {
method: 'POST',
})
.then(response => response.json())
.then(output => printOutput(output))
.catch(err => printOutput(err));
}
// Show the #results div and print the output to the #output preformatted block.
function printOutput(output) {
console.log(output);
document.getElementById('results').style.display = 'initial';
document.getElementById('output').innerText = output.toString();
// Scroll to top of the screen.
window.scrollTo(0, 0);
}
Finally, to run our web application, we'll use the following command:
$ node server.js
Congratulations, you have now created a web application that can manage files in a PDS using Node.js with zoau!