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

Fix #3192: Reloading With --update-env Option #5881

Open
wants to merge 5 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/dotenvx-pm2/.env.initial
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DE=dotenv_initial
SH_DE=dotenv_initial
DE_PM=dotenv_initial
SH_DE_PM=dotenv_initial
4 changes: 4 additions & 0 deletions examples/dotenvx-pm2/.env.updated
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DE=dotenv_updated
SH_DE=dotenv_updated
DE_PM=dotenv_updated
SH_DE_PM=dotenv_updated
21 changes: 21 additions & 0 deletions examples/dotenvx-pm2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
This is an example of using [dotenvx](https://dotenvx.com/) with pm2.

It demonstrates how to update the environment for forked and clustered apps using dotenvx.

In this example, the environment variables come from three sources and are applied in the following order (dotenv files take precedence because of the `--overload` option used):
- shell environment
- dotenv files `.env.initial` or `.env.updated`.
- pm2 ecosystem config `ecosystem.config.cjs`

See `package.json` scripts for the actual commands.

```bash
# install dotenvx
npm install
# start apps with `initial` environment and `.env.initial` dotenv file
npm run start:json
# reload apps with `updated` environment and `.env.updated` dotenv file
npm run reload:json
# delete apps
npm run delete:json
```
42 changes: 42 additions & 0 deletions examples/dotenvx-pm2/ecosystem.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module.exports = {
apps: [
{
name: 'forked_app',
script: './index.js',
env_initial: {
PORT: 8001,
PM: 'pm2_initial',
SH_PM: 'pm2_initial',
DE_PM: 'pm2_initial',
SH_DE_PM: 'pm2_initial',
},
env_updated: {
PORT: 8001,
PM: 'pm2_updated',
SH_PM: 'pm2_updated',
DE_PM: 'pm2_updated',
SH_DE_PM: 'pm2_updated',
},
},
{
name: 'clustered_app',
script: './index.js',
instances: 2,
exec_mode: 'cluster',
env_initial: {
PORT: 8002,
PM: 'pm2_initial',
SH_PM: 'pm2_initial',
DE_PM: 'pm2_initial',
SH_DE_PM: 'pm2_initial',
},
env_updated: {
PORT: 8002,
PM: 'pm2_updated',
SH_PM: 'pm2_updated',
DE_PM: 'pm2_updated',
SH_DE_PM: 'pm2_updated',
},
},
],
};
27 changes: 27 additions & 0 deletions examples/dotenvx-pm2/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import http from 'http';

const {
PORT,
SH,
DE,
PM,
SH_DE,
SH_PM,
DE_PM,
SH_DE_PM,
} = process.env;

http.createServer((req, res) => {
res.writeHead(200);
res.end(JSON.stringify({
SH,
DE,
PM,
SH_DE,
SH_PM,
DE_PM,
SH_DE_PM,
}, null, 2));
}).listen(PORT, '0.0.0.0', () => {
console.log(`App listening on port ${PORT}`);
});
18 changes: 18 additions & 0 deletions examples/dotenvx-pm2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "dotenvx-pm2",
"version": "1.0.0",
"description": "Example usage of dotenvx with pm2",
"main": "index.js",
"type": "module",
"author": "Michael Kalygin",
"license": "MIT",
"scripts": {
"start:json": "SH=shell_initial SH_DE=shell_initial SH_PM=shell_initial SH_DE_PM=shell_initial dotenvx run --env-file .env.initial --overload -- ../../bin/pm2 start ecosystem.config.cjs --env initial",
"reload:json": "SH=shell_updated SH_DE=shell_updated SH_PM=shell_updated SH_DE_PM=shell_updated dotenvx run --env-file .env.updated --overload -- ../../bin/pm2 reload ecosystem.config.cjs --env updated --update-env",
"delete:json": "../../bin/pm2 delete ecosystem.config.cjs",
"test:json": "../../bin/pm2 update && npm run delete:json && npm run start:json && npm run reload:json"
},
"dependencies": {
"@dotenvx/dotenvx": "^1.10.2"
}
}
2 changes: 1 addition & 1 deletion lib/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,7 @@ class API {
});

// When we are processing JSON, allow to keep the new env by default
env.updateEnv = true;
env.updateEnv = typeof(opts.updateEnv) === 'undefined' ? true : opts.updateEnv;

// Pass `env` option
that._operate(action, proc_name, env, function(err, ret) {
Expand Down
8 changes: 4 additions & 4 deletions lib/Utility.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ var Utility = module.exports = {
},
extendExtraConfig : function(proc, opts) {
if (opts.env && opts.env.current_conf) {
if (opts.env.current_conf.env &&
typeof(opts.env.current_conf.env) === 'object' &&
Object.keys(opts.env.current_conf.env).length === 0)
delete opts.env.current_conf.env
// NOTE: `env` gets overriden by `Utility.extendMix` call. Instead, we want to merge envs.
if (opts.env.current_conf.env && typeof(opts.env.current_conf.env) === 'object')
Utility.extendMix(proc.pm2_env.env, opts.env.current_conf.env);
delete opts.env.current_conf.env;

Utility.extendMix(proc.pm2_env, opts.env.current_conf);
delete opts.env.current_conf;
Expand Down
Empty file modified test/e2e.sh
100644 → 100755
Empty file.
95 changes: 95 additions & 0 deletions test/e2e/cli/env-refresh.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,98 @@ $pm2 kill
$pm2 l
NODE_PATH='/test2' $pm2 start local_require.js -i 1
should 'should have loaded the right globalPaths' 'restart_time: 0' 1

#
# Ensuring that environment update works correctly when reloading with JSON config.
#
# Related issue:
# https://github.com/Unitech/pm2/issues/3192
#

# start with config
SH=shell_initial SH_PM=shell_initial $pm2 start update-env.config.js --env initial
>out-env.log

sleep 0.5
grep "SH=shell_initial PM=pm2_initial SH_PM=pm2_initial" out-env.log &> /dev/null
spec "should inject shell environment, then inject config environment on start with config"

# restart config without --update-env
$pm2 delete all
SH=shell_initial SH_PM=shell_initial $pm2 start update-env.config.js --env initial
SH=shell_updated SH_PM=shell_updated $pm2 restart update-env.config.js --env updated
>out-env.log

sleep 0.5
grep "SH=shell_updated PM=pm2_updated SH_PM=pm2_updated" out-env.log &> /dev/null
spec "should inject shell environment, then inject config environment on restart with config and without --update-env option"

# reload config without --update-env
$pm2 delete all
SH=shell_initial SH_PM=shell_initial $pm2 start update-env.config.js --env initial
SH=shell_updated SH_PM=shell_updated $pm2 reload update-env.config.js --env updated
>out-env.log

sleep 0.5
grep "SH=shell_updated PM=pm2_updated SH_PM=pm2_updated" out-env.log &> /dev/null
spec "should inject shell environment, then inject config environment on reload with config and without --update-env option"

# restart config with --update-env
$pm2 delete all
SH=shell_initial SH_PM=shell_initial $pm2 start update-env.config.js --env initial
SH=shell_updated SH_PM=shell_updated $pm2 restart update-env.config.js --env updated --update-env
>out-env.log

sleep 0.5
grep "SH=shell_updated PM=pm2_updated SH_PM=pm2_updated" out-env.log &> /dev/null
spec "should inject shell environment, then inject config environment on restart with config and with --update-env option"

# reload config with --update-env
$pm2 delete all
SH=shell_initial SH_PM=shell_initial $pm2 start update-env.config.js --env initial
SH=shell_updated SH_PM=shell_updated $pm2 reload update-env.config.js --env updated --update-env
>out-env.log

sleep 0.5
grep "SH=shell_updated PM=pm2_updated SH_PM=pm2_updated" out-env.log &> /dev/null
spec "should inject shell environment, then inject config environment on reload with config and with --update-env option"

# restart pid without --update-env
$pm2 delete all
SH=shell_initial SH_PM=shell_initial $pm2 start update-env.config.js --env initial
SH=shell_updated SH_PM=shell_updated $pm2 restart update_env_app
>out-env.log

sleep 0.5
grep "SH=shell_initial PM=pm2_initial SH_PM=pm2_initial" out-env.log &> /dev/null
spec "should keep environment on restart with pid and without --update-env option"

# reload pid without --update-env
$pm2 delete all
SH=shell_initial SH_PM=shell_initial $pm2 start update-env.config.js --env initial
SH=shell_updated SH_PM=shell_updated $pm2 reload update_env_app
>out-env.log

sleep 0.5
grep "SH=shell_initial PM=pm2_initial SH_PM=pm2_initial" out-env.log &> /dev/null
spec "should keep environment on reload with pid and without --update-env option"

# restart pid with --update-env
$pm2 delete all
SH=shell_initial SH_PM=shell_initial $pm2 start update-env.config.js --env initial
SH=shell_updated SH_PM=shell_updated $pm2 restart update_env_app --update-env
>out-env.log

sleep 0.5
grep "SH=shell_updated PM=pm2_initial SH_PM=shell_updated" out-env.log &> /dev/null
spec "should inject shell environment on restart with pid and with --update-env option"

# reload pid with --update-env
$pm2 delete all
SH=shell_initial SH_PM=shell_initial $pm2 start update-env.config.js --env initial
SH=shell_updated SH_PM=shell_updated $pm2 reload update_env_app --update-env
>out-env.log

sleep 0.5
grep "SH=shell_updated PM=pm2_initial SH_PM=shell_updated" out-env.log &> /dev/null
spec "should inject shell environment on reload with pid and with --update-env option"
22 changes: 22 additions & 0 deletions test/fixtures/update-env.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = {
apps: [
{
name: 'update_env_app',
script: './update-env.js',
instances: 2,
exec_mode: 'cluster',
out_file: 'out-env.log',
merge_logs: true,
env_initial: {
NODE_ENV: 'test',
PM: 'pm2_initial',
SH_PM: 'pm2_initial',
},
env_updated: {
NODE_ENV: 'test',
PM: 'pm2_updated',
SH_PM: 'pm2_updated',
},
},
],
};
5 changes: 5 additions & 0 deletions test/fixtures/update-env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
setInterval(() => {
const { SH, PM, SH_PM } = process.env;

console.log(`SH=${SH} PM=${PM} SH_PM=${SH_PM}`);
}, 100);
119 changes: 111 additions & 8 deletions test/programmatic/env_switching.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,117 @@ describe('PM2 programmatic calls', function() {
});
});

// it('should start a script and NODE_ENV have right value', function(done) {
// pm2.start(json_declaration_simple, function(err, data) {
// proc1 = data[0];
// should(err).be.null;
// proc1.pm2_env['NODE_ENV'].should.eql(json_declaration.env.NODE_ENV);
// done();
// });
// });
/**
* Ensuring that environment update works correctly when reloading with JSON config.
*
* Related issue:
* https://github.com/Unitech/pm2/issues/3192
*/
describe('with updateEnv option', () => {
const env = {
shell: {
initial: {
SH: 'shell_initial',
SH_PM: 'shell_initial',
},
updated: {
SH: 'shell_updated',
SH_PM: 'shell_updated',
},
},
pm2: {
initial: {
PM: 'pm2_initial',
SH_PM: 'pm2_initia',
},
updated: {
PM: 'pm2_updated',
SH_PM: 'pm2_updated',
},
},
};

const configInitial = {
name: 'child-update-env',
script: './../fixtures/env-switching/child.js',
instances: '2',
env: {
NODE_ENV: 'test',
...env.pm2.initial,
},
};

const configUpdated = {
...configInitial,
env: {
NODE_ENV: 'test',
...env.pm2.updated,
},
};

it('should inject shell environment, then inject config environment on start', (done) => {
Object.assign(process.env, env.shell.initial);

pm2.start(configInitial, (err, data) => {
try {
const pm2Env = data[0] ? data[0].pm2_env || {} : {};
should(err).be.null();
should(pm2Env.SH).eql(env.shell.initial.SH);
should(pm2Env.PM).eql(env.pm2.initial.PM);
should(pm2Env.SH_PM).eql(env.pm2.initial.SH_PM);
done();
} catch (err) {
done(err);
}
});
});

it('should inject only config environment on restart when disabled', (done) => {
Object.assign(process.env, env.shell.updated);

pm2.restart(configUpdated, { updateEnv: false }, (err) => {
should(err).be.null();

pm2.list((err, data) => {
try {
const pm2Env = data.find(proc => proc.name === configInitial.name).pm2_env;
should(err).be.null();
should(pm2Env.SH).eql(env.shell.initial.SH);
should(pm2Env.PM).eql(env.pm2.updated.PM);
should(pm2Env.SH_PM).eql(env.pm2.updated.SH_PM);
done();
} catch (err) {
done(err);
}
});
});
});

it('should inject shell environment, then inject config environment on start when endabled', (done) => {
Object.assign(process.env, env.shell.updated);

pm2.restart(configUpdated, { updateEnv: true }, (err) => {
should(err).be.null();

pm2.list((err, data) => {
try {
const pm2Env = data.find(proc => proc.name === configInitial.name).pm2_env;
should(err).be.null();
should(pm2Env.SH).eql(env.shell.updated.SH);
should(pm2Env.PM).eql(env.pm2.updated.PM);
should(pm2Env.SH_PM).eql(env.pm2.updated.SH_PM);
done();
} catch (err) {
done(err);
}
});
});
});

it('should delete all processes', (done) => {
pm2.delete('all', (err, ret) => {
done();
});
});
});
});
Empty file modified test/unit.sh
100644 → 100755
Empty file.