Sunday, 21 April 2019

child_process.spawn() hangs when child process prompts for input

I've written a node script to manage deployment of a git repository to a AWS autoscaling group.

The script uses child_process.spawn() to automate git, to clone repositories, checkout tags etc.

It works fine if git can find appropriate credentials. However if credentials aren't automatically found, then the spawned process will attempt to prompt for credentials, and at that point will hang. Even Ctrl-C cannot exit. The whole shell session must be ended.

The spawn() call is wrapped in a function to return a Promise. My function looks like so...

const cp = require('child_process');

let spawn_promise = (command, args, options, stream_output) => {
    return new Promise((resolve, reject) => {
        console.log(chalk.cyan(`${command} [${args}]`));

        let childProcess = cp.spawn(command, args, options);
        let std_out = '';
        let std_err = '';

        childProcess.stdout.on('data', function (data) {
            std_out += data.toString();
            if (stream_output)
                console.log(chalk.green(data.toString()));
        });
        childProcess.stderr.on('data', function (data) {
            std_err += data.toString();
            if (stream_output)
                console.log(chalk.red(data.toString()));
        });

        childProcess.on('close', (code) => {
            if (code === 0) {
                console.log(chalk.blue(`exit_code = ${code}`));
                return resolve(std_out);
            }
            else {
                console.log(chalk.yellow(`exit_code = ${code}`));
                return reject(std_err);
            }
        });

        childProcess.on('error', (error) => {
            std_err += error.toString();
            if (stream_output)
                console.log(chalk.red(error.toString()));
        });
    });
}

I call it like so...

return spawn_promise('git', ['fetch', '--all'], {env: process.env})
   .then(() => {
      ...

It mostly works very well, and allows easily manipulation of output and errors etc.

I'm having trouble figuring out a nice way to to handle input though, if a spawned process needs it.

A temporary work-around for the problem is to add an environment variable to prevent git from prompting for credentials, and instead to throw an error if it can't find credentials in the users environment. However this isn't ideal. Ideally I would like to be able to gracefully handle standard input, and still be able to capture and process the output and errors as I'm currently doing.

I can fix the problem with input by doing this...

let childProcess = cp.spawn(command, args, { stdio: [process.stdin, process.stdout, process.stderr] });

This allows git to prompt for credentials correctly. However I then lose the ability to capture the command output.

What is the correct way to be able to handle this?

I should also mention, that the function also automates some relatively long running processes, to build AMI's etc. This is what the "stream_output" parameter is for. I want to be able to view the output from the command in real-time, rather than waiting until the process completes.



from child_process.spawn() hangs when child process prompts for input

No comments:

Post a Comment