terraguardian-cli 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terraguardian-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI that helps with projects that are built with terraform and helps with deployments of lambda src code",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"src",
|
|
11
11
|
"bin"
|
|
12
|
-
|
|
13
12
|
],
|
|
14
13
|
"scripts": {
|
|
15
14
|
"test": "jest",
|
|
@@ -30,7 +29,7 @@
|
|
|
30
29
|
"dependencies": {
|
|
31
30
|
"archiver": "^7.0.0",
|
|
32
31
|
"chalk": "^4.0.0",
|
|
33
|
-
"inquirer": "^8.
|
|
32
|
+
"inquirer": "^8.2.6",
|
|
34
33
|
"yargs": "^17.7.2"
|
|
35
34
|
},
|
|
36
35
|
"devDependencies": {
|
package/src/common/helpers.js
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
+
const { executeCommand } = require('../../common/helpers');
|
|
3
|
+
const FileHandler = require('../../fs-utils/FileHandler');
|
|
4
|
+
const { findEntryPoints, getNormalizedLambdaNames } = require('./helpers');
|
|
2
5
|
|
|
3
6
|
const {
|
|
4
7
|
LAMBDA_BUILD_DIR,
|
|
5
|
-
WEBPACK_FILE_PATH,
|
|
6
|
-
NODE_LIBARY_TYPE,
|
|
7
|
-
DEFAULT_WEBPACK_MODE,
|
|
8
8
|
NODE_BUILD_FILE_NAME,
|
|
9
9
|
NODE_RUNTIME_TF_VAR,
|
|
10
10
|
} = require('../../common/constants');
|
|
11
|
-
const { executeCommand } = require('../../common/helpers');
|
|
12
|
-
const FileHandler = require('../../fs-utils/FileHandler');
|
|
13
|
-
const { findEntryPoints, getNormalizedLambdaNames } = require('./helpers');
|
|
14
|
-
|
|
15
|
-
function getWebpackBuildCommand(lambdaName, lambdaPath, buildOutputDir) {
|
|
16
|
-
let buildCommand;
|
|
17
|
-
const webpackExists = FileHandler.doesFileExist('webpack.config.js');
|
|
18
11
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
// Create esbuild build command
|
|
13
|
+
function getEsbuildBuildCommand(lambdaName, lambdaPath, buildOutputDir) {
|
|
14
|
+
const buildCommand = [
|
|
15
|
+
'npx esbuild',
|
|
16
|
+
lambdaPath, // Input file (entry point)
|
|
17
|
+
'--bundle', // Bundle the files together
|
|
18
|
+
'--platform=node', // Set platform to node for AWS Lambda
|
|
19
|
+
'--target=node20', // Change as needed for your Lambda runtime
|
|
20
|
+
`--outfile=${buildOutputDir}/${NODE_BUILD_FILE_NAME}`, // Output file name
|
|
21
|
+
'--sourcemap', // Optional: generate sourcemaps for debugging
|
|
22
|
+
'--minify', // Optional: minify the output (you can disable if not needed)
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
return buildCommand.join(' ');
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
async function buildNodeLambda(lambdaName, lambdaPath) {
|
|
@@ -31,7 +30,7 @@ async function buildNodeLambda(lambdaName, lambdaPath) {
|
|
|
31
30
|
|
|
32
31
|
const buildOutputDir = `${LAMBDA_BUILD_DIR}/${lambdaName}`;
|
|
33
32
|
|
|
34
|
-
const buildCommand =
|
|
33
|
+
const buildCommand = getEsbuildBuildCommand(lambdaName, lambdaPath, buildOutputDir);
|
|
35
34
|
console.log(await executeCommand(buildCommand));
|
|
36
35
|
console.log(chalk.green('Successfully built the Lambda'));
|
|
37
36
|
return `${buildOutputDir}/${NODE_BUILD_FILE_NAME}`;
|
|
@@ -40,12 +39,10 @@ async function buildNodeLambda(lambdaName, lambdaPath) {
|
|
|
40
39
|
async function buildNodeLambdas() {
|
|
41
40
|
const lambdaPaths = findEntryPoints(NODE_RUNTIME_TF_VAR);
|
|
42
41
|
const normalizedLambdaNames = getNormalizedLambdaNames(lambdaPaths);
|
|
42
|
+
|
|
43
43
|
if (lambdaPaths.length > 0) {
|
|
44
|
-
if (FileHandler.doesPathExist('./node_modules')) {
|
|
45
|
-
console.log('node_modules
|
|
46
|
-
} else {
|
|
47
|
-
console.log('node_modules does not exist');
|
|
48
|
-
console.log('Installing node_modules');
|
|
44
|
+
if (!FileHandler.doesPathExist('./node_modules')) {
|
|
45
|
+
console.log('node_modules does not exist, installing...');
|
|
49
46
|
const { stdout, stderr } = await executeCommand('npm install');
|
|
50
47
|
console.log(stdout);
|
|
51
48
|
console.log(stderr);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { executeCommand } = require('../../common/helpers');
|
|
2
|
+
|
|
3
|
+
// uses the aws cli to deploy a lambda to AWS
|
|
4
|
+
function deployLambda(lambdaName, zipFileName) {
|
|
5
|
+
const deployCommand = `aws lambda update-function-code --function-name ${lambdaName} --zip-file fileb://dist/${zipFileName}.zip 1> /dev/null`;
|
|
6
|
+
return executeCommand(deployCommand);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
deployLambda,
|
|
11
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
// Find all .tf files recursively
|
|
7
|
+
function getTerraformFiles(dir) {
|
|
8
|
+
let files = [];
|
|
9
|
+
/* eslint-disable-next-line */
|
|
10
|
+
for (const file of fs.readdirSync(dir)) {
|
|
11
|
+
const fullPath = path.join(dir, file);
|
|
12
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
13
|
+
files = files.concat(getTerraformFiles(fullPath));
|
|
14
|
+
} else if (file.endsWith('.tf')) {
|
|
15
|
+
files.push(fullPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return files;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function validateTerraformCode() {
|
|
22
|
+
const ROOT_DIR = process.cwd();
|
|
23
|
+
const BUILD_DIR = path.join(ROOT_DIR, 'tf-build');
|
|
24
|
+
const LOCK_FILE = path.join(BUILD_DIR, '.terraform.lock.hcl');
|
|
25
|
+
const HASH_FILE = path.join(ROOT_DIR, '.tf-build-hash');
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
console.log(`Setting up Terraform validation in ${BUILD_DIR}...`);
|
|
29
|
+
|
|
30
|
+
// Ensure tf-build exists and remove old Terraform files (but keep .terraform/ if it exists)
|
|
31
|
+
if (fs.existsSync(BUILD_DIR)) {
|
|
32
|
+
fs.readdirSync(BUILD_DIR).forEach((file) => {
|
|
33
|
+
const filePath = path.join(BUILD_DIR, file);
|
|
34
|
+
if (!file.startsWith('.terraform')) {
|
|
35
|
+
fs.rmSync(filePath, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
fs.mkdirSync(BUILD_DIR, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tfFiles = getTerraformFiles(ROOT_DIR);
|
|
43
|
+
|
|
44
|
+
// Generate a hash of all Terraform files
|
|
45
|
+
const hash = crypto.createHash('sha256');
|
|
46
|
+
tfFiles.forEach((file) => {
|
|
47
|
+
hash.update(fs.readFileSync(file));
|
|
48
|
+
});
|
|
49
|
+
const currentHash = hash.digest('hex');
|
|
50
|
+
|
|
51
|
+
// Check if the hash has changed since the last run
|
|
52
|
+
let previousHash = '';
|
|
53
|
+
if (fs.existsSync(HASH_FILE)) {
|
|
54
|
+
previousHash = fs.readFileSync(HASH_FILE, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const providersChanged = !fs.existsSync(LOCK_FILE) || previousHash !== currentHash;
|
|
58
|
+
|
|
59
|
+
// Copy files with modified names
|
|
60
|
+
tfFiles.forEach((file) => {
|
|
61
|
+
const relativePath = path.relative(ROOT_DIR, file);
|
|
62
|
+
const modifiedName = relativePath.replace(/\//g, '-');
|
|
63
|
+
const destPath = path.join(BUILD_DIR, modifiedName);
|
|
64
|
+
fs.copyFileSync(file, destPath);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Run terraform init only if providers changed
|
|
68
|
+
if (providersChanged) {
|
|
69
|
+
console.log('Providers changed, running terraform init...');
|
|
70
|
+
execSync('terraform init -backend=false -input=false -no-color', { cwd: BUILD_DIR, stdio: 'inherit' });
|
|
71
|
+
fs.writeFileSync(HASH_FILE, currentHash); // Save new hash
|
|
72
|
+
} else {
|
|
73
|
+
console.log('No provider changes detected, skipping terraform init.');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Run terraform validate
|
|
77
|
+
execSync('terraform validate', { cwd: BUILD_DIR, stdio: 'inherit' });
|
|
78
|
+
|
|
79
|
+
console.log('Terraform validation successful.');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error('Terraform validation failed:', error.message);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { validateTerraformCode };
|
package/src/index.js
CHANGED
|
@@ -4,9 +4,19 @@ const yargs = require('yargs');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const fs = require('fs');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
|
|
7
9
|
const { zipAndMoveFolders } = require('./features/build/zip');
|
|
8
10
|
const { buildNodeLambdas } = require('./features/build/node-build');
|
|
9
|
-
const { deployLambda } = require('./features/deploy');
|
|
11
|
+
const { deployLambda } = require('./features/deploy/lambda');
|
|
12
|
+
const { validateTerraformCode } = require('./features/validate-terraform/validate');
|
|
13
|
+
|
|
14
|
+
// A function that gets the names of the zip files in the dist folder and filters out the extension
|
|
15
|
+
function getZipNames(dirPath) {
|
|
16
|
+
const files = fs.readdirSync(dirPath);
|
|
17
|
+
const zipFiles = files.filter((file) => path.extname(file) === '.zip').map((file) => path.basename(file, '.zip'));
|
|
18
|
+
return zipFiles;
|
|
19
|
+
}
|
|
10
20
|
|
|
11
21
|
const usage = chalk.keyword('violet')('\nUsage: tg-cli [<command> [, options]]');
|
|
12
22
|
const argv = yargs
|
|
@@ -27,6 +37,11 @@ const argv = yargs
|
|
|
27
37
|
describe: 'deploy a lambda',
|
|
28
38
|
type: 'boolean',
|
|
29
39
|
})
|
|
40
|
+
.option('v', {
|
|
41
|
+
alias: 'validate-tf',
|
|
42
|
+
describe: 'validate terraform code even if it is nested',
|
|
43
|
+
type: 'boolean',
|
|
44
|
+
})
|
|
30
45
|
.help(true)
|
|
31
46
|
.alias('h', 'help').argv;
|
|
32
47
|
|
|
@@ -47,10 +62,31 @@ async function executeUserCommand(userCommands) {
|
|
|
47
62
|
console.log(`Zipping complete. Details saved to ${resultFilePath}`);
|
|
48
63
|
} else if (userCommands.d || userCommands.deploy) {
|
|
49
64
|
console.log('deploying');
|
|
50
|
-
|
|
65
|
+
// get all the zip folder names from the dist folder
|
|
66
|
+
const lambdasOptions = getZipNames('dist');
|
|
67
|
+
const response = await inquirer.prompt([
|
|
68
|
+
{
|
|
69
|
+
type: 'list',
|
|
70
|
+
name: 'deployOptions',
|
|
71
|
+
message: 'Which Lambda would you like to deploy?',
|
|
72
|
+
choices: lambdasOptions,
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
// get name of the lambda to deploy
|
|
76
|
+
const res = await inquirer.prompt([
|
|
77
|
+
{
|
|
78
|
+
type: 'input',
|
|
79
|
+
name: 'nameOfLambda',
|
|
80
|
+
message: 'What is the name of the lambda?',
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
await deployLambda(res.nameOfLambda, response.deployOptions);
|
|
51
84
|
} else if (userCommands.N || userCommands['node-build']) {
|
|
52
85
|
console.log('building node lambda');
|
|
53
86
|
await buildNodeLambdas();
|
|
87
|
+
} else if (userCommands.v || userCommands['validate-tf']) {
|
|
88
|
+
console.log('Validating terraform code');
|
|
89
|
+
await validateTerraformCode();
|
|
54
90
|
} else {
|
|
55
91
|
yargs.showHelp();
|
|
56
92
|
}
|
package/src/features/deploy.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const inquirer = require('inquirer');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
|
|
5
|
-
const { getDetailedLambdaInfo } = require('./scan/tfModulesScanner');
|
|
6
|
-
const { zipLambdas } = require('./build/zip');
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
NODE_RUNTIME_TF_VAR,
|
|
10
|
-
GO_RUNTIME_TF_VAR,
|
|
11
|
-
LAMBDA_BUILD_DIR,
|
|
12
|
-
NODE_ENTRY_POINT,
|
|
13
|
-
GO_ENTRY_POINT,
|
|
14
|
-
} = require('../common/constants');
|
|
15
|
-
const { getBranchPrefix, executeCommand } = require('../common/helpers');
|
|
16
|
-
const { findEntryPoints } = require('./build/helpers');
|
|
17
|
-
const { buildNodeLambda } = require('./build/node-build');
|
|
18
|
-
const { buildGoLambda } = require('./build/go-build');
|
|
19
|
-
|
|
20
|
-
const SUPPORTED_RUNTIMES = [NODE_RUNTIME_TF_VAR, GO_RUNTIME_TF_VAR];
|
|
21
|
-
|
|
22
|
-
async function deployToAws(zipOutputDir, awsLambdaName) {
|
|
23
|
-
console.log(chalk.blue(`Deploying ${awsLambdaName} to AWS...`));
|
|
24
|
-
|
|
25
|
-
const deployCommand = `aws lambda update-function-code --function-name ${awsLambdaName} --zip-file fileb://${zipOutputDir} 1> /dev/null`;
|
|
26
|
-
await executeCommand(deployCommand);
|
|
27
|
-
console.log(chalk.green(`Successfully deployed ${awsLambdaName} to AWS!`));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function deployLambda() {
|
|
31
|
-
const lambdaResources = await getDetailedLambdaInfo(path.resolve(), '.tf');
|
|
32
|
-
const lambdaToDeployPrompt = {
|
|
33
|
-
type: 'list',
|
|
34
|
-
name: 'lambdaToDeploy',
|
|
35
|
-
message: 'Choose a lambda to deploy to AWS',
|
|
36
|
-
choices: lambdaResources
|
|
37
|
-
.filter((res) => SUPPORTED_RUNTIMES.includes(res.runtime))
|
|
38
|
-
.map((res) => res.lambdaName),
|
|
39
|
-
};
|
|
40
|
-
const response = await inquirer.createPromptModule(lambdaToDeployPrompt);
|
|
41
|
-
const { terraformName, runtime, lambdaName } = lambdaResources.find(
|
|
42
|
-
(res) => res.lambdaName === response.lambdaToDeploy,
|
|
43
|
-
);
|
|
44
|
-
const awsLambdaName = `${await getBranchPrefix()}-${lambdaName}}`;
|
|
45
|
-
|
|
46
|
-
console.log(`Full name is ${awsLambdaName}`);
|
|
47
|
-
const runtimeEntryPoint = {
|
|
48
|
-
[NODE_RUNTIME_TF_VAR]: NODE_ENTRY_POINT,
|
|
49
|
-
[GO_RUNTIME_TF_VAR]: GO_ENTRY_POINT,
|
|
50
|
-
}[runtime];
|
|
51
|
-
const entryPointPath = findEntryPoints(runtimeEntryPoint, terraformName);
|
|
52
|
-
|
|
53
|
-
const buildFunction = {
|
|
54
|
-
[NODE_RUNTIME_TF_VAR]: buildNodeLambda,
|
|
55
|
-
[GO_RUNTIME_TF_VAR]: buildGoLambda,
|
|
56
|
-
}[runtime];
|
|
57
|
-
await buildFunction(terraformName, entryPointPath);
|
|
58
|
-
|
|
59
|
-
const zipOutputDir = await zipLambdas(LAMBDA_BUILD_DIR, [terraformName]);
|
|
60
|
-
await deployToAws(`dist/${zipOutputDir}`, awsLambdaName);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
module.exports = {
|
|
64
|
-
deployLambda,
|
|
65
|
-
};
|