Ad

FTP In AWS Lambda - Issues Downloading Files (Async/Await)

I have been struggling with various FTP Node modules to try and get anything working in AWS Lambda. The best and most popular seems to be "Basic-FTP" that also supports async/await. But I just cannot get it to download files when any code is added beneath the FTP function.

I don't want to add the fs functions within the FTP async function as I need to solve what is causing the break when any code below is added and I also have other bits of code to add and work with the downloaded file and it's content later:

FTP SUCCESS - When the async function is used only with no fs code beneath it

FTP FAILURE - Adding the fs readdir/readFile functions or any other code below

ERRORError: ENOENT: no such file or directory, open '/tmp/document.txt'

https://github.com/patrickjuchli/basic-ftp

const ftp = require("basic-ftp");
const fs = require("fs");
var FileNameWithExtension = "document.txt";
var ftpTXT;

exports.handler = async (event, context, callback) => {

    example();

    async function example() {
        const client = new ftp.Client();
        //client.ftp.verbose = true;
        try {
            await client.access({
                host: host,
                user: user,
                password: password,
                //secure: true
            });
            console.log(await client.list());
            await client.download(fs.createWriteStream('/tmp/' + FileNameWithExtension), FileNameWithExtension);
        }
        catch (err) {
            console.log(err);
        }
        client.close();
    }

    // Read the content from the /tmp/ directory to check FTP was succesful
    fs.readdir("/tmp/", function (err, data) {
        if (err) {
            return console.error("There was an error listing the /tmp/ contents.");
        }
        console.log('Contents of AWS Lambda /tmp/ directory: ', data);
    });

    // Read TXT file and convert into string format
    fs.readFile('/tmp/' + FileNameWithExtension, 'utf8', function (err, data) {
        if (err) throw err;
        ftpTXT = data;
        console.log(ftpTXT);
    });

    // Do other Node.js coding with the downloaded txt file and it's contents
};
Ad

Answer

The problem is that you are getting lost when creating an async function inside your handler. Since example() is async, it returns a Promise. But you don't await on it, so the way it has been coded, it's kind of a fire and forget thing. Also, your Lambda is being terminated before your callbacks are triggered, so even if it got to download you would not be able to see it.

I suggest you wrap your callbacks in Promises so you can easily await on them from your handler function.

I have managed to make it work: I have used https://dlptest.com/ftp-test/ for testing, so change it accordingly. Furthermore, see that I have uploaded the file myself. So if you want to replicate this example, just create a readme.txt on the root of your project and upload it. If you already have this readme.txt file on your FTP server, just delete the line where it uploads the file.

Here's a working example:

const ftp = require("basic-ftp");
const fs = require("fs");
const FileNameWithExtension = "readme.txt";

module.exports.hello = async (event) => {

  const client = new ftp.Client();
  try {
    await client.access({
      host: 'ftp.dlptest.com',
      user: '[email protected]',
      password: 'puTeT3Yei1IJ4UYT7q0r'
    });
    console.log(await client.list());
    await client.upload(fs.createReadStream(FileNameWithExtension), FileNameWithExtension)
    await client.download(fs.createWriteStream('/tmp/' + FileNameWithExtension), FileNameWithExtension);
  }
  catch (err) {
    console.log('logging err')
    console.log(err);
  }
  client.close();

  console.log(await readdir('/tmp/'))

  console.log(await readfile('/tmp/', FileNameWithExtension))

  return {
    statusCode: 200,
    body: JSON.stringify({message: 'File downloaded successfully'})
  }

};

const readdir = dir => {
  return new Promise((res, rej) => {
    fs.readdir(dir, function (err, data) {
      if (err) {
        return rej(err);
      }
      return res(data)
    });
  })
}

const readfile = (dir, filename) => {
  return new Promise((res, rej) => {
    fs.readFile(dir + filename, 'utf8', function (err, data) {
      if (err) {
        return rej(err);
      }
      return res(data)
    })
  })
}

Here is the output of the Lambda function:

enter image description here

And here are the complete CloudWatch logs:

enter image description here

My file contains nothing but a 'hello' inside it. You can see it on the logs.

Do keep in mind that, in Lambda Functions, you have a 512MB limit when downloading anything to /tmp. You can see the limits in the docs

Ad
source: stackoverflow.com
Ad