How does child_process behave in Nodejs - node.js

I have a working Nodejs code, however, the child_process library behaves strange, I am just wondering how this library works.
My code is trying to download the SSL certificates from S3, then create the two new files based on the exiting ones using child_process library.
const AWS = require('aws-sdk');
const fs = require('fs');
const child_process = require("child_process");
const exec = require('child_process').exec;
var s3 = new AWS.S3();
var filePath = '../Desktop/Certs/'
var bucketName = 'neb.certificates' //replace example bucket with your s3 bucket name
var params = {
Bucket: bucketName,
Prefix: 'dev/jenkins.secure.care/',
};
s3.listObjectsV2(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
// console.log(data.Contents)
var len = data.Contents.length
for(var i=0; i<len; i++){
var key = data.Contents[i]["Key"]
var newPath = filePath.concat(key.substring(31))
const downloadFile = (newPath, bucketName, key) => {
//construct getParam
var getParams = {
Bucket: bucketName,
Key: key
};
s3.getObject(getParams, (err, data) => {
if (err) console.error(err)
fs.writeFileSync(newPath, data.Body.toString())
// console.log(`${newPath} has been created!`)
})
}
downloadFile(newPath, bucketName, key)
}
}
});
exec('mv ../Desktop/Certs/cert.pem ../Desktop/Certs/jenkins.crt', (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
});
exec('mv ../Desktop/Certs/privkey.pem ../Desktop/Certs/jenkins.key', (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
});
So when I run the code the first time, it only downloads the certificates from S3 to the local folder, it did not create the other 2 files. Then I have to run it the second time to create additional files.
However, I just want to run it once, and it has everything what I expect.
I have added a code which to sleep 5 seconds, then create the additional files, But it did not solve my problem, which means I still run the code twice to get everything.
child_process.execSync("sleep 5");
Please helps

It looks like the problem here is the async execution of your code. In this example here's what's happening:
Run s3.listObjectsV2() and when finished, fire callback (but not now, in the future)
Run first exec() and when finished, fire callback (but not now, in the future)
Run second exec() and when finished, fire callback (but not now, in the future)
And those three steps are fired immediately, one by one. And each of them has its own callback which fires in the future. Ok, but when is the future? - exactly! You don't know. In your case probably those two callbacks in exec()'s are fired before the callback from s3 and this is why it does not work.
The solution here is to make sure that those exec()s are fired after s3.listObjects. So you have two options: first is to make a promise out of the s3, like this: s3.listObjectsV2(params).promise() and await for it, then in .then((data) => {}) you have your data and in .catch((error) => {}) you have your error. Or you can simply put those exec()s in the callback of the s3 call.
Your code should look like this according to the solution 2 (from the comments):
const AWS = require('aws-sdk');
const fs = require('fs');
const child_process = require("child_process");
const exec = require('child_process').exec;
var s3 = new AWS.S3();
var filePath = '../Desktop/Certs/'
var bucketName = 'neb.certificates' //replace example bucket with your s3 bucket name
var params = {
Bucket: bucketName,
Prefix: 'dev/jenkins.secure.care/',
};
s3.listObjectsV2(params, async function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
// console.log(data.Contents)
var len = data.Contents.length
for(var i=0; i<len; i++){
var key = data.Contents[i]["Key"]
var newPath = filePath.concat(key.substring(31))
const downloadFile = (newPath, bucketName, key) => {
//construct getParam
var getParams = {
Bucket: bucketName,
Key: key
};
return s3.getObject(getParams).promise();
};
const downloadData = await downloadFile(newPath, bucketName, key).catch(console.error);
fs.writeFileSync(newPath, downloadData.Body.toString());
console.log(newPath, 'created');
}
//rename files
console.log('renaming first cert.pem');
exec('mv ../Desktop/Certs/cert.pem ../Desktop/Certs/jenkins.crt', (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
});
console.log('renaming second privkey.pem');
exec('mv ../Desktop/Certs/privkey.pem ../Desktop/Certs/jenkins.key', (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
});
}
});

Related

JIMP Issues with saving within /tmp in AWS Lambda function

I have written a Lambda function which opens an image and modifies it, before saving it to the /tmp folder and uploading it to an S3 bucket. This works locally, but when I run in in Lambda I get an error stating no such file or directory, open '/tmp/nft.png. What could be causing this? I originally thought that it was an issue with the write function not being awaited, but I don't think this can be the case since it works fine locally.
var Jimp = require("jimp")
var fs = require('fs')
var path = require("path")
var AWS = require('aws-sdk')
AWS.config.update({
accessKeyId: <removed>,
secretAccessKey: <removed>
})
var s3 = new AWS.S3()
async function updateImage() {
var img = await Jimp.read("base_img.png")
var font = await Jimp.loadFont("fonts/Audiowide.fnt")
img.print(<removed for simplicity>)
return img.write("/tmp/nft.png")
}
function uploadFile(id) {
return new Promise((resolve, reject) => {
fs.readFile("/tmp/nft.png", function (err, data) {
if (err) { throw err; }
params = {<removed>};
s3.putObject(params, function(err, data) {
if (err) {
console.log(err)
reject(err)
} else {
console.log("Successfully uploaded data");
resolve()
}
});
});
})
}
exports.handler = async (event) => {
await updateImage()
await uploadFile(event.queryStringParameters.id)
return {
statusCode: 200,
body: JSON.stringify("Generated image")
}
}

Node : Wait the python script to write the file then upload it to s3

I have done the following code. Where I create a file by a python script then upload it to S3 then give the user the ability to download it.
exports.createFeature = async (req, res, next) => {
let retourUrl = await uploadFile(req.body)
res.status(201).json(retourUrl)
};
function uploadFile(feature) {
return new Promise(async (resolve, reject) => {
let options = {
scriptPath: 'pathToDcript',
args: [arg1, arg2, arg3]
};
PythonShell.run('script.py', options, function (err) {
if (err) throw err;
console.log('file has been created !');
//read the file
let contents = fs.readFileSync('pathToFile', {encoding:'utf8', flag:'r'});
//convert it to buffer
const fileContent = Buffer.from(contents, "utf-8");
// Setting up S3 upload parameters
let key = keyUserData+feature.userId+'/fileName'
const params = {
Bucket: bucket,
Key: key, // File name you want to save as in S3
Body: fileContent
};
// Uploading files to the bucket
s3.upload(params, function(err, data) {
if (err) {
throw err;
}
//console.log(`File uploaded successfully. ${data.Location}`);
});
// delete the file
fs.unlinkSync('pathToFile');
//get url for download
const presignedURL = s3.getSignedUrl('getObject', {
Bucket: bucket,
Key: key,
Expires: 60*5
})
resolve(presignedURL)
})
});
}
But I have the download url before the file is been uploaded to S3, any idea on how I can make it wait till all finish ?
if you want to use s3.upload with a callback. you need to change your code as mentioned below.
exports.createFeature = async (req, res, next) => {
let retourUrl = await uploadFile(req.body)
res.status(201).json(retourUrl)
};
function uploadFile(feature) {
return new Promise((resolve, reject) => {
let options = {
scriptPath: 'pathToDcript',
args: [arg1, arg2, arg3]
};
PythonShell.run('script.py', options, function (err) {
if (err) throw err;
console.log('file has been created !');
//read the file
let contents = fs.readFileSync('pathToFile', { encoding: 'utf8', flag: 'r' });
//convert it to buffer
const fileContent = Buffer.from(contents, "utf-8");
// Setting up S3 upload parameters
let key = keyUserData + feature.userId + '/fileName'
const params = {
Bucket: bucket,
Key: key, // File name you want to save as in S3
Body: fileContent
};
// Uploading files to the bucket
s3.upload(params, function (err, data) {
if (err) {
throw err;
}
// delete the file
fs.unlinkSync('pathToFile');
//get url for download
const presignedURL = s3.getSignedUrl('getObject', {
Bucket: bucket,
Key: key,
Expires: 60 * 5
})
//console.log(`File uploaded successfully. ${data.Location}`);
resolve(presignedURL)
});
})
});
}
The S3 upload method of the AWS SDK returns a Promise which can be awaited on.
For example:
await s3.upload(...)
Note that in this case the callback function to the Python script should be changed to an async function, in order to allow the await syntax. For example:
PythonShell.run('script.py', options, async function (err)

AWS S3 Lambda WriteFile from stream

Using an AWS Lambda, I just want to get an archive (lo.tar.gz) from my s3, copy it to the tmp folder and extract it to the tmp folder.
I have this code in local who work perfectly :
var AWS = require('aws-sdk');
var fs = require('fs');
var targz = require('targz');
var s3 = new AWS.S3({ apiVersion: '2006-03-01' });
var fileStream = fs.createWriteStream('./tmp/lo.tar.gz');
var s3Stream = s3.getObject({ Bucket: 'mybucketname', Key: 'lo.tar.gz' }).createReadStream();
//console.log("Here the last console.log who appear");
// Listen for errors returned by the service
s3Stream.on('error', function (err) {
// NoSuchKey: The specified key does not exist
console.error(err);
});
s3Stream.pipe(fileStream).on('error', function (err) {
// capture any errors that occur when writing data to the file
console.error('File Stream:', err);
}).on('close', function () {
console.log('Stream Done.');
// decompress files from tar.gz archive
targz.decompress({
src: './tmp/lo.tar.gz',
dest: './tmp'
}, function (err) {
if (err) {
console.log(err);
} else {
console.log("Extract Done!");
}
});
});
But when I put it in a lambda, nothing append.
It creates an empty file "lo.tar.gz" in tmp and it closes saying "statut:succeeded" and without any error message ... I put "console.log" between each line and the last to appear is the one I left ("Here the last console.log who appear") ...
Does anyone have any idea why the code is not being executed to the end?
Thank you :D!
EDIT : here the exact code for the lambda (the same with exports.handler) :
var AWS = require('aws-sdk');
var fs = require('fs');
var targz = require('targz');
exports.handler = async (event, context, callback) => {
var s3 = new AWS.S3({ apiVersion: '2006-03-01' });
var fileStream = fs.createWriteStream('./tmp/lo.tar.gz');
var s3Stream = s3.getObject({ Bucket: 'mybucketname', Key: 'lo.tar.gz'
}).createReadStream();
//console.log("Here the last console.log who appear");
// Listen for errors returned by the service
s3Stream.on('error', function (err) {
// NoSuchKey: The specified key does not exist
console.error(err);
});
s3Stream.pipe(fileStream).on('error', function (err) {
// capture any errors that occur when writing data to the file
console.error('File Stream:', err);
}).on('close', function () {
console.log('Stream Done.');
// decompress files from tar.gz archive
targz.decompress({
src: './tmp/lo.tar.gz',
dest: './tmp'
}, function (err) {
if (err) {
console.log(err);
} else {
console.log("Extract Done!");
}
});
});
}

azure fileservice createFileFromText with nodejs. is only uploading 15 B I have 16mb file

I am trying to send file from s3 to azure using lambda. but it is timing out and uploading only 15B.
'use strict';
var AWS = require('aws-sdk');
var azure = require('azure-storage');
var s3 = new AWS.S3();
var xml2js = require('xml2js');
var parser = new xml2js.Parser({explicitArray : false});
var extractedData = "";
exports.handler = function(event, context) {
var bucketName = event.Records[0].s3.bucket.name;
var keyName = event.Records[0].s3.object.key;
readFile(bucketName, keyName, readFileContent, onError);
};
function readFile (bucketName, filename, onFileContent, onError) {
var params = { Bucket: bucketName, Key: filename };
s3.getObject(params, function (err, data) {
if (!err)
onFileContent(filename, data.Body);
else
console.log(err);
});
}
function readFileContent(filename, content) {
parser.parseString(content, function(err,result){
console.log(result);
var fileService = azure.createFileService('fgdgdfg','dfgdfgdfgdfgdfg');
fileService.createFileFromText('xfiles', '', 'rros.json', result, function(error, result, response) {
if (!error) {
console.log("file created....")
}else{
console.log(error)
}
});
});
}
function onError (err) {
console.log('error: ' + err);
}
ERROR:
TypeError: First argument must be a string or Buffer
at ClientRequest.OutgoingMessage.write (_http_outgoing.js:458:11)
at Request.write (/var/task/node_modules/request/request.js:1501:27)
The fourth parameter of the function createFileFromText(...) doesn't accept a JavaScript object. You should use JSON.stringify() to covert it to a string like this:
fileService.createFileFromText('xfiles', '', 'rros.json', JSON.stringify(result), function(error, result, response) {
if (!error) {
console.log("file created....")
}else{
console.log(error)
}
});

Read file from aws s3 bucket using node fs

I am attempting to read a file that is in a aws s3 bucket using
fs.readFile(file, function (err, contents) {
var myLines = contents.Body.toString().split('\n')
})
I've been able to download and upload a file using the node aws-sdk, but I am at a loss as to how to simply read it and parse the contents.
Here is an example of how I am reading the file from s3:
var s3 = new AWS.S3();
var params = {Bucket: 'myBucket', Key: 'myKey.csv'}
var s3file = s3.getObject(params)
You have a couple options. You can include a callback as a second argument, which will be invoked with any error message and the object. This example is straight from the AWS documentation:
s3.getObject(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Alternatively, you can convert the output to a stream. There's also an example in the AWS documentation:
var s3 = new AWS.S3({apiVersion: '2006-03-01'});
var params = {Bucket: 'myBucket', Key: 'myImageFile.jpg'};
var file = require('fs').createWriteStream('/path/to/file.jpg');
s3.getObject(params).createReadStream().pipe(file);
This will do it:
new AWS.S3().getObject({ Bucket: this.awsBucketName, Key: keyName }, function(err, data)
{
if (!err)
console.log(data.Body.toString());
});
Since you seem to want to process an S3 text file line-by-line. Here is a Node version that uses the standard readline module and AWS' createReadStream()
const readline = require('readline');
const rl = readline.createInterface({
input: s3.getObject(params).createReadStream()
});
rl.on('line', function(line) {
console.log(line);
})
.on('close', function() {
});
If you are looking to avoid the callbacks you can take advantage of the sdk .promise() function like this:
const s3 = new AWS.S3();
const params = {Bucket: 'myBucket', Key: 'myKey.csv'}
const response = await s3.getObject(params).promise() // await the promise
const fileContent = response.Body.toString('utf-8'); // can also do 'base64' here if desired
I'm sure the other ways mentioned here have their advantages but this works great for me. Sourced from this thread (see the last response from AWS): https://forums.aws.amazon.com/thread.jspa?threadID=116788
here is the example which i used to retrive and parse json data from s3.
var params = {Bucket: BUCKET_NAME, Key: KEY_NAME};
new AWS.S3().getObject(params, function(err, json_data)
{
if (!err) {
var json = JSON.parse(new Buffer(json_data.Body).toString("utf8"));
// PROCESS JSON DATA
......
}
});
I couldn't figure why yet, but the createReadStream/pipe approach didn't work for me. I was trying to download a large CSV file (300MB+) and I got duplicated lines. It seemed a random issue. The final file size varied in each attempt to download it.
I ended up using another way, based on AWS JS SDK examples:
var s3 = new AWS.S3();
var params = {Bucket: 'myBucket', Key: 'myImageFile.jpg'};
var file = require('fs').createWriteStream('/path/to/file.jpg');
s3.getObject(params).
on('httpData', function(chunk) { file.write(chunk); }).
on('httpDone', function() { file.end(); }).
send();
This way, it worked like a charm.
I prefer Buffer.from(data.Body).toString('utf8'). It supports encoding parameters. With other AWS services (ex. Kinesis Streams) someone may want to replace 'utf8' encoding with 'base64'.
new AWS.S3().getObject(
{ Bucket: this.awsBucketName, Key: keyName },
function(err, data) {
if (!err) {
const body = Buffer.from(data.Body).toString('utf8');
console.log(body);
}
}
);
I had exactly the same issue when downloading from S3 very large files.
The example solution from AWS docs just does not work:
var file = fs.createWriteStream(options.filePath);
file.on('close', function(){
if(self.logger) self.logger.info("S3Dataset file download saved to %s", options.filePath );
return callback(null,done);
});
s3.getObject({ Key: documentKey }).createReadStream().on('error', function(err) {
if(self.logger) self.logger.error("S3Dataset download error key:%s error:%#", options.fileName, error);
return callback(error);
}).pipe(file);
While this solution will work:
var file = fs.createWriteStream(options.filePath);
s3.getObject({ Bucket: this._options.s3.Bucket, Key: documentKey })
.on('error', function(err) {
if(self.logger) self.logger.error("S3Dataset download error key:%s error:%#", options.fileName, error);
return callback(error);
})
.on('httpData', function(chunk) { file.write(chunk); })
.on('httpDone', function() {
file.end();
if(self.logger) self.logger.info("S3Dataset file download saved to %s", options.filePath );
return callback(null,done);
})
.send();
The createReadStream attempt just does not fire the end, close or error callback for some reason. See here about this.
I'm using that solution also for writing down archives to gzip, since the first one (AWS example) does not work in this case either:
var gunzip = zlib.createGunzip();
var file = fs.createWriteStream( options.filePath );
s3.getObject({ Bucket: this._options.s3.Bucket, Key: documentKey })
.on('error', function (error) {
if(self.logger) self.logger.error("%#",error);
return callback(error);
})
.on('httpData', function (chunk) {
file.write(chunk);
})
.on('httpDone', function () {
file.end();
if(self.logger) self.logger.info("downloadArchive downloaded %s", options.filePath);
fs.createReadStream( options.filePath )
.on('error', (error) => {
return callback(error);
})
.on('end', () => {
if(self.logger) self.logger.info("downloadArchive unarchived %s", options.fileDest);
return callback(null, options.fileDest);
})
.pipe(gunzip)
.pipe(fs.createWriteStream(options.fileDest))
})
.send();
With the new version of sdk, the accepted answer does not work - it does not wait for the object to be downloaded. The following code snippet will help with the new version:
// dependencies
const AWS = require('aws-sdk');
// get reference to S3 client
const s3 = new AWS.S3();
exports.handler = async (event, context, callback) => {
var bucket = "TestBucket"
var key = "TestKey"
try {
const params = {
Bucket: Bucket,
Key: Key
};
var theObject = await s3.getObject(params).promise();
} catch (error) {
console.log(error);
return;
}
}
If you want to save memory and want to obtain each row as a json object, then you can use fast-csv to create readstream and can read each row as a json object as follows:
const csv = require('fast-csv');
const AWS = require('aws-sdk');
const credentials = new AWS.Credentials("ACCESSKEY", "SECRETEKEY", "SESSIONTOKEN");
AWS.config.update({
credentials: credentials, // credentials required for local execution
region: 'your_region'
});
const dynamoS3Bucket = new AWS.S3();
const stream = dynamoS3Bucket.getObject({ Bucket: 'your_bucket', Key: 'example.csv' }).createReadStream();
var parser = csv.fromStream(stream, { headers: true }).on("data", function (data) {
parser.pause(); //can pause reading using this at a particular row
parser.resume(); // to continue reading
console.log(data);
}).on("end", function () {
console.log('process finished');
});
var fileStream = fs.createWriteStream('/path/to/file.jpg');
var s3Stream = s3.getObject({Bucket: 'myBucket', Key: 'myImageFile.jpg'}).createReadStream();
// Listen for errors returned by the service
s3Stream.on('error', function(err) {
// NoSuchKey: The specified key does not exist
console.error(err);
});
s3Stream.pipe(fileStream).on('error', function(err) {
// capture any errors that occur when writing data to the file
console.error('File Stream:', err);
}).on('close', function() {
console.log('Done.');
});
Reference: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/requests-using-stream-objects.html

Resources