Merge pull request #5 from easingthemes/feature/SSH-DEPLOY-refactor

Feature/ssh deploy refactor
main
Dragan Filipović 2020-04-11 17:25:18 +02:00 committed by GitHub
commit 0711330570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 2100 additions and 299 deletions

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4
trim_trailing_whitespace = true

25
.eslintrc.js Normal file
View File

@ -0,0 +1,25 @@
module.exports = {
env: {
commonjs: true,
es6: true,
node: true
},
extends: [
'airbnb-base'
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
parserOptions: {
ecmaVersion: 2018,
},
rules: {
"comma-dangle": [
"error",
"never"
],
"no-console": "off",
"object-curly-newline": "off"
}
};

2
.gitignore vendored
View File

@ -17,5 +17,5 @@ node_modules/
.env .env
.env.test .env.test
# jetbrains # IDE
.idea .idea

View File

@ -14,11 +14,13 @@ Pass configuration with `env` vars
1. `SSH_PRIVATE_KEY` [required] 1. `SSH_PRIVATE_KEY` [required]
This should be the private key part of an ssh key pair. This should be the private key part of an ssh key pair.
The public key part should be added to the authorized_keys file on the server that receives the deployment. The public key part should be added to the authorized_keys file on the server that receives the deployment.
The keys should be generated using the PEM format. You can us this command
The keys should be generated using the PEM format. You can use this command ```
`ssh-keygen -m PEM -t rsa -b 4096` ssh-keygen -m PEM -t rsa -b 4096
```
2. `REMOTE_HOST` [required] 2. `REMOTE_HOST` [required]
@ -93,6 +95,4 @@ jobs:
## Disclaimer ## Disclaimer
If you're using GitHub Actions, you'll probably already know that it's still in limited public beta, and GitHub advise against using Actions in production. Check your keys. Check your deployment paths. And use at your own risk.
So, check your keys. Check your deployment paths. And use at your own risk.

View File

@ -13,12 +13,15 @@ inputs:
required: true required: true
REMOTE_PORT: REMOTE_PORT:
description: "Remote port" description: "Remote port"
required: false
default: "22" default: "22"
SOURCE: SOURCE:
description: "Source directory" description: "Source directory"
required: false
default: "" default: ""
TARGET: TARGET:
description: "Target directory" description: "Target directory"
required: false
default: "/home/REMOTE_USER/" default: "/home/REMOTE_USER/"
outputs: outputs:
status: status:

350
dist/index.js vendored
View File

@ -49,6 +49,51 @@ module.exports =
module.exports = require("child_process"); module.exports = require("child_process");
/***/ }),
/***/ 197:
/***/ (function(module, __unusedexports, __webpack_require__) {
const { existsSync, mkdirSync, writeFileSync } = __webpack_require__(747);
const {
GITHUB_WORKSPACE
} = process.env;
const validateDir = (dir) => {
if (!existsSync(dir)) {
console.log(`[SSH] Creating ${dir} dir in `, GITHUB_WORKSPACE);
mkdirSync(dir);
console.log('✅ [SSH] dir created.');
} else {
console.log(`[SSH] ${dir} dir exist`);
}
};
const validateFile = (filePath) => {
if (!existsSync(filePath)) {
console.log(`[SSH] Creating ${filePath} file in `, GITHUB_WORKSPACE);
try {
writeFileSync(filePath, '', {
encoding: 'utf8',
mode: 0o600
});
console.log('✅ [SSH] file created.');
} catch (e) {
console.error('⚠️ [SSH] writeFileSync error', filePath, e.message);
process.abort();
}
} else {
console.log(`[SSH] ${filePath} file exist`);
}
};
module.exports = {
validateDir,
validateFile
};
/***/ }), /***/ }),
/***/ 243: /***/ 243:
@ -455,6 +500,50 @@ function getString(command,callback){
module.exports=commandline; module.exports=commandline;
/***/ }),
/***/ 613:
/***/ (function(module, __unusedexports, __webpack_require__) {
const { writeFileSync } = __webpack_require__(747);
const { join } = __webpack_require__(622);
const {
validateDir,
validateFile
} = __webpack_require__(197);
const {
HOME
} = process.env;
const addSshKey = (key, name) => {
const sshDir = join(HOME || __dirname, '.ssh');
const filePath = join(sshDir, name);
validateDir(sshDir);
validateFile(`${sshDir}/known_hosts`);
try {
writeFileSync(filePath, key, {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
console.log('✅ Ssh key added to `.ssh` dir ', filePath);
return filePath;
};
module.exports = {
addSshKey
}
/***/ }), /***/ }),
/***/ 622: /***/ 622:
@ -474,164 +563,82 @@ module.exports = require("util");
/***/ 676: /***/ 676:
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) { /***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
const fs = __webpack_require__(747);
const path = __webpack_require__(622);
const commandExists = __webpack_require__(677);
const nodeCmd = __webpack_require__(428);
const nodeRsync = __webpack_require__(250); const nodeRsync = __webpack_require__(250);
const { REMOTE_HOST, REMOTE_USER, REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME, SOURCE, TARGET, ARGS, GITHUB_WORKSPACE, HOME } = process.env; const { validateRsync, validateInputs } = __webpack_require__(735);
console.log('GITHUB_WORKSPACE', GITHUB_WORKSPACE); const { addSshKey } = __webpack_require__(613);
const sshDeploy = (() => { const {
const rsync = ({ privateKey, port, src, dest, args }) => { REMOTE_HOST, REMOTE_USER,
console.log(`Starting Rsync Action: ${src} to ${dest}`); REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME,
SOURCE, TARGET, ARGS,
GITHUB_WORKSPACE
} = process.env;
try { const defaultOptions = {
// RSYNC COMMAND ssh: true,
nodeRsync({ src, dest, args, privateKey, ssh: true, port, sshCmdArgs: ['-o StrictHostKeyChecking=no'], recursive: true }, (error, stdout, stderr, cmd) => { sshCmdArgs: ['-o StrictHostKeyChecking=no'],
if (error) { recursive: true
console.error('⚠️ Rsync error', error.message);
console.log('stderr: ', stderr);
console.log('stdout: ', stdout);
console.log('cmd: ', cmd);
process.abort();
} else {
console.log("✅ Rsync finished.", stdout);
}
});
} catch (err) {
console.error(`⚠️ An error happened:(.`, err.message, err.stack);
process.abort();
}
};
const init = ({
src,
dest,
args,
host = 'localhost',
username,
privateKeyContent,
port
}) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME ||'deploy_key');
const remoteDest = username + '@' + host + ':' + dest;
rsync({ privateKey, port, src, dest: remoteDest, args });
});
};
const validateDir = (dir) => {
if (!fs.existsSync(dir)){
console.log(`Creating ${dir} dir in `, GITHUB_WORKSPACE);
fs.mkdirSync(dir);
} else {
console.log(`${dir} dir exist`);
}
};
const validateFile = (filePath) => {
if (!fs.existsSync(filePath)){
console.log(`Creating ${filePath} file in `, GITHUB_WORKSPACE);
try {
fs.writeFileSync(filePath, '', {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
} else {
console.log(`${filePath} file exist`);
}
};
const addSshKey = (key, name) => {
const sshDir = path.join(HOME || __dirname, '.ssh');
const filePath = path.join(sshDir, name);
validateDir(sshDir);
validateFile(sshDir + '/known_hosts');
try {
fs.writeFileSync(filePath, key, {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
console.log('✅ Ssh key added to `.ssh` dir ', filePath);
return filePath;
};
const validateRsync = (callback = () => {}) => {
const rsyncCli = commandExists.sync('rsync');
if (!rsyncCli) {
nodeCmd.get(
'sudo apt-get --no-install-recommends install rsync',
function(err, data, stderr){
if (err) {
console.log('⚠️ Rsync installation failed ', err.message);
process.abort();
} else {
console.log('✅ Rsync installed. \n', data, stderr);
callback();
}
}
);
} else {
callback();
}
};
return {
init
}
})();
const validateInputs = (inputs) => {
const validInputs = Object.keys(inputs).filter((key) => {
const input = inputs[key];
if (!input) {
console.error(`⚠️ ${key} is mandatory`);
}
return input;
});
if (validInputs.length !== Object.keys(inputs).length) {
process.abort();
}
}; };
const run = () => { console.log('[general] GITHUB_WORKSPACE: ', GITHUB_WORKSPACE);
validateInputs({SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER});
sshDeploy.init({ const sshDeploy = (() => {
src: GITHUB_WORKSPACE + '/' + SOURCE || '', const rsync = ({ privateKey, port, src, dest, args }) => {
dest: TARGET || '/home/' + REMOTE_USER + '/', console.log(`[Rsync] Starting Rsync Action: ${src} to ${dest}`);
args: ARGS ? [ARGS] : ['-rltgoDzvO'],
host: REMOTE_HOST, try {
port: REMOTE_PORT || '22', // RSYNC COMMAND
username: REMOTE_USER, nodeRsync({
privateKeyContent: SSH_PRIVATE_KEY, src, dest, args, privateKey, port, ...defaultOptions
}, (error, stdout, stderr, cmd) => {
if (error) {
console.error('⚠️ [Rsync] error: ', error.message);
console.log('⚠️ [Rsync] stderr: ', stderr);
console.log('⚠️ [Rsync] stdout: ', stdout);
console.log('⚠️ [Rsync] cmd: ', cmd);
process.abort();
} else {
console.log('✅ [Rsync] finished.', stdout);
}
});
} catch (err) {
console.error('⚠️ [Rsync] command error: ', err.message, err.stack);
process.abort();
}
};
const init = ({ src, dest, args, host = 'localhost', port, username, privateKeyContent }) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME || 'deploy_key');
const remoteDest = `${username}@${host}:${dest}`;
rsync({ privateKey, port, src, dest: remoteDest, args });
}); });
};
return {
init
};
})();
const run = () => {
validateInputs({ SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER });
sshDeploy.init({
src: `${GITHUB_WORKSPACE}/${SOURCE}` || '',
dest: TARGET || `/home/${REMOTE_USER}/`,
args: ARGS ? [ARGS] : ['-rltgoDzvO'],
host: REMOTE_HOST,
port: REMOTE_PORT || '22',
username: REMOTE_USER,
privateKeyContent: SSH_PRIVATE_KEY
});
}; };
run(); run();
/***/ }), /***/ }),
/***/ 677: /***/ 677:
@ -640,6 +647,59 @@ run();
module.exports = __webpack_require__(243); module.exports = __webpack_require__(243);
/***/ }),
/***/ 735:
/***/ (function(module, __unusedexports, __webpack_require__) {
const { sync: commandExists } = __webpack_require__(677);
const { get: nodeCmd } = __webpack_require__(428);
const validateRsync = (callback = () => {}) => {
const rsyncCli = commandExists.sync('rsync');
if (!rsyncCli) {
nodeCmd(
'sudo apt-get --no-install-recommends install rsync',
(err, data, stderr) => {
if (err) {
console.log('⚠️ [CLI] Rsync installation failed. Aborting ... ', err.message);
process.abort();
} else {
console.log('✅ [CLI] Rsync installed. \n', data, stderr);
callback();
}
}
);
} else {
callback();
}
};
const validateInputs = (inputs) => {
const inputKeys = Object.keys(inputs);
const validInputs = inputKeys.filter((inputKey) => {
const inputValue = inputs[inputKey];
if (!inputValue) {
console.error(`⚠️ [INPUTS] ${inputKey} is mandatory`);
}
return inputValue;
});
if (validInputs.length !== inputKeys.length) {
console.error(`⚠️ [INPUTS] Inputs not valid, aborting ...`);
process.abort();
}
};
module.exports = {
validateRsync,
validateInputs
}
/***/ }), /***/ }),
/***/ 747: /***/ 747:

1658
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,15 @@
"rsyncwrapper": "3.0.1" "rsyncwrapper": "3.0.1"
}, },
"devDependencies": { "devDependencies": {
"@zeit/ncc": "^0.20.5" "@zeit/ncc": "^0.20.5",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2"
}, },
"scripts": { "scripts": {
"build": "ncc build ./src/index.js -o dist" "build": "npm run lint && ncc build ./src/index.js -o dist",
"lint": "eslint ./src/index.js",
"lint:fix": "eslint ./src/index.js --fix"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

38
src/helpers.js Normal file
View File

@ -0,0 +1,38 @@
const { existsSync, mkdirSync, writeFileSync } = require('fs');
const {
GITHUB_WORKSPACE
} = process.env;
const validateDir = (dir) => {
if (!existsSync(dir)) {
console.log(`[SSH] Creating ${dir} dir in `, GITHUB_WORKSPACE);
mkdirSync(dir);
console.log('✅ [SSH] dir created.');
} else {
console.log(`[SSH] ${dir} dir exist`);
}
};
const validateFile = (filePath) => {
if (!existsSync(filePath)) {
console.log(`[SSH] Creating ${filePath} file in `, GITHUB_WORKSPACE);
try {
writeFileSync(filePath, '', {
encoding: 'utf8',
mode: 0o600
});
console.log('✅ [SSH] file created.');
} catch (e) {
console.error('⚠️ [SSH] writeFileSync error', filePath, e.message);
process.abort();
}
} else {
console.log(`[SSH] ${filePath} file exist`);
}
};
module.exports = {
validateDir,
validateFile
};

View File

@ -1,157 +1,75 @@
#!/usr/bin/env node #!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const commandExists = require('command-exists');
const nodeCmd = require('node-cmd');
const nodeRsync = require('rsyncwrapper'); const nodeRsync = require('rsyncwrapper');
const { REMOTE_HOST, REMOTE_USER, REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME, SOURCE, TARGET, ARGS, GITHUB_WORKSPACE, HOME } = process.env; const { validateRsync, validateInputs } = require('./rsyncCli');
console.log('GITHUB_WORKSPACE', GITHUB_WORKSPACE); const { addSshKey } = require('./sshKey');
const sshDeploy = (() => { const {
const rsync = ({ privateKey, port, src, dest, args }) => { REMOTE_HOST, REMOTE_USER,
console.log(`Starting Rsync Action: ${src} to ${dest}`); REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME,
SOURCE, TARGET, ARGS,
GITHUB_WORKSPACE
} = process.env;
try { const defaultOptions = {
// RSYNC COMMAND ssh: true,
nodeRsync({ src, dest, args, privateKey, ssh: true, port, sshCmdArgs: ['-o StrictHostKeyChecking=no'], recursive: true }, (error, stdout, stderr, cmd) => { sshCmdArgs: ['-o StrictHostKeyChecking=no'],
if (error) { recursive: true
console.error('⚠️ Rsync error', error.message);
console.log('stderr: ', stderr);
console.log('stdout: ', stdout);
console.log('cmd: ', cmd);
process.abort();
} else {
console.log("✅ Rsync finished.", stdout);
}
});
} catch (err) {
console.error(`⚠️ An error happened:(.`, err.message, err.stack);
process.abort();
}
};
const init = ({
src,
dest,
args,
host = 'localhost',
username,
privateKeyContent,
port
}) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME ||'deploy_key');
const remoteDest = username + '@' + host + ':' + dest;
rsync({ privateKey, port, src, dest: remoteDest, args });
});
};
const validateDir = (dir) => {
if (!fs.existsSync(dir)){
console.log(`Creating ${dir} dir in `, GITHUB_WORKSPACE);
fs.mkdirSync(dir);
} else {
console.log(`${dir} dir exist`);
}
};
const validateFile = (filePath) => {
if (!fs.existsSync(filePath)){
console.log(`Creating ${filePath} file in `, GITHUB_WORKSPACE);
try {
fs.writeFileSync(filePath, '', {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
} else {
console.log(`${filePath} file exist`);
}
};
const addSshKey = (key, name) => {
const sshDir = path.join(HOME || __dirname, '.ssh');
const filePath = path.join(sshDir, name);
validateDir(sshDir);
validateFile(sshDir + '/known_hosts');
try {
fs.writeFileSync(filePath, key, {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
console.log('✅ Ssh key added to `.ssh` dir ', filePath);
return filePath;
};
const validateRsync = (callback = () => {}) => {
const rsyncCli = commandExists.sync('rsync');
if (!rsyncCli) {
nodeCmd.get(
'sudo apt-get --no-install-recommends install rsync',
function(err, data, stderr){
if (err) {
console.log('⚠️ Rsync installation failed ', err.message);
process.abort();
} else {
console.log('✅ Rsync installed. \n', data, stderr);
callback();
}
}
);
} else {
callback();
}
};
return {
init
}
})();
const validateInputs = (inputs) => {
const validInputs = Object.keys(inputs).filter((key) => {
const input = inputs[key];
if (!input) {
console.error(`⚠️ ${key} is mandatory`);
}
return input;
});
if (validInputs.length !== Object.keys(inputs).length) {
process.abort();
}
}; };
const run = () => { console.log('[general] GITHUB_WORKSPACE: ', GITHUB_WORKSPACE);
validateInputs({SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER});
sshDeploy.init({ const sshDeploy = (() => {
src: GITHUB_WORKSPACE + '/' + SOURCE || '', const rsync = ({ privateKey, port, src, dest, args }) => {
dest: TARGET || '/home/' + REMOTE_USER + '/', console.log(`[Rsync] Starting Rsync Action: ${src} to ${dest}`);
args: ARGS ? [ARGS] : ['-rltgoDzvO'],
host: REMOTE_HOST, try {
port: REMOTE_PORT || '22', // RSYNC COMMAND
username: REMOTE_USER, nodeRsync({
privateKeyContent: SSH_PRIVATE_KEY, src, dest, args, privateKey, port, ...defaultOptions
}, (error, stdout, stderr, cmd) => {
if (error) {
console.error('⚠️ [Rsync] error: ', error.message);
console.log('⚠️ [Rsync] stderr: ', stderr);
console.log('⚠️ [Rsync] stdout: ', stdout);
console.log('⚠️ [Rsync] cmd: ', cmd);
process.abort();
} else {
console.log('✅ [Rsync] finished.', stdout);
}
});
} catch (err) {
console.error('⚠️ [Rsync] command error: ', err.message, err.stack);
process.abort();
}
};
const init = ({ src, dest, args, host = 'localhost', port, username, privateKeyContent }) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME || 'deploy_key');
const remoteDest = `${username}@${host}:${dest}`;
rsync({ privateKey, port, src, dest: remoteDest, args });
}); });
};
return {
init
};
})();
const run = () => {
validateInputs({ SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER });
sshDeploy.init({
src: `${GITHUB_WORKSPACE}/${SOURCE}` || '',
dest: TARGET || `/home/${REMOTE_USER}/`,
args: ARGS ? [ARGS] : ['-rltgoDzvO'],
host: REMOTE_HOST,
port: REMOTE_PORT || '22',
username: REMOTE_USER,
privateKeyContent: SSH_PRIVATE_KEY
});
}; };
run(); run();

46
src/rsyncCli.js Normal file
View File

@ -0,0 +1,46 @@
const { sync: commandExists } = require('command-exists');
const { get: nodeCmd } = require('node-cmd');
const validateRsync = (callback = () => {}) => {
const rsyncCli = commandExists.sync('rsync');
if (!rsyncCli) {
nodeCmd(
'sudo apt-get --no-install-recommends install rsync',
(err, data, stderr) => {
if (err) {
console.log('⚠️ [CLI] Rsync installation failed. Aborting ... ', err.message);
process.abort();
} else {
console.log('✅ [CLI] Rsync installed. \n', data, stderr);
callback();
}
}
);
} else {
callback();
}
};
const validateInputs = (inputs) => {
const inputKeys = Object.keys(inputs);
const validInputs = inputKeys.filter((inputKey) => {
const inputValue = inputs[inputKey];
if (!inputValue) {
console.error(`⚠️ [INPUTS] ${inputKey} is mandatory`);
}
return inputValue;
});
if (validInputs.length !== inputKeys.length) {
console.error(`⚠️ [INPUTS] Inputs not valid, aborting ...`);
process.abort();
}
};
module.exports = {
validateRsync,
validateInputs
}

37
src/sshKey.js Normal file
View File

@ -0,0 +1,37 @@
const { writeFileSync } = require('fs');
const { join } = require('path');
const {
validateDir,
validateFile
} = require('./helpers');
const {
HOME
} = process.env;
const addSshKey = (key, name) => {
const sshDir = join(HOME || __dirname, '.ssh');
const filePath = join(sshDir, name);
validateDir(sshDir);
validateFile(`${sshDir}/known_hosts`);
try {
writeFileSync(filePath, key, {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
console.log('✅ Ssh key added to `.ssh` dir ', filePath);
return filePath;
};
module.exports = {
addSshKey
}