@salte-common/terraflow 0.1.0-alpha.1
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/LICENSE +22 -0
- package/README.md +278 -0
- package/RELEASE_SUMMARY.md +53 -0
- package/STANDARDS_COMPLIANCE.md +85 -0
- package/bin/terraflow.js +3 -0
- package/bin/tf.js +3 -0
- package/dist/commands/apply.d.ts +7 -0
- package/dist/commands/apply.js +12 -0
- package/dist/commands/base.d.ts +7 -0
- package/dist/commands/base.js +12 -0
- package/dist/commands/config.d.ts +25 -0
- package/dist/commands/config.js +354 -0
- package/dist/commands/destroy.d.ts +7 -0
- package/dist/commands/destroy.js +12 -0
- package/dist/commands/init.d.ts +68 -0
- package/dist/commands/init.js +131 -0
- package/dist/commands/plan.d.ts +7 -0
- package/dist/commands/plan.js +12 -0
- package/dist/core/backend-state.d.ts +25 -0
- package/dist/core/backend-state.js +77 -0
- package/dist/core/config.d.ts +83 -0
- package/dist/core/config.js +295 -0
- package/dist/core/context.d.ts +52 -0
- package/dist/core/context.js +192 -0
- package/dist/core/environment.d.ts +62 -0
- package/dist/core/environment.js +205 -0
- package/dist/core/errors.d.ts +22 -0
- package/dist/core/errors.js +36 -0
- package/dist/core/plugin-loader.d.ts +21 -0
- package/dist/core/plugin-loader.js +136 -0
- package/dist/core/terraform.d.ts +45 -0
- package/dist/core/terraform.js +247 -0
- package/dist/core/validator.d.ts +103 -0
- package/dist/core/validator.js +304 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +184 -0
- package/dist/plugins/auth/aws-assume-role.d.ts +10 -0
- package/dist/plugins/auth/aws-assume-role.js +110 -0
- package/dist/plugins/auth/azure-service-principal.d.ts +10 -0
- package/dist/plugins/auth/azure-service-principal.js +99 -0
- package/dist/plugins/auth/gcp-service-account.d.ts +10 -0
- package/dist/plugins/auth/gcp-service-account.js +105 -0
- package/dist/plugins/backends/azurerm.d.ts +10 -0
- package/dist/plugins/backends/azurerm.js +117 -0
- package/dist/plugins/backends/gcs.d.ts +10 -0
- package/dist/plugins/backends/gcs.js +75 -0
- package/dist/plugins/backends/local.d.ts +11 -0
- package/dist/plugins/backends/local.js +37 -0
- package/dist/plugins/backends/s3.d.ts +10 -0
- package/dist/plugins/backends/s3.js +185 -0
- package/dist/plugins/secrets/aws-secrets.d.ts +12 -0
- package/dist/plugins/secrets/aws-secrets.js +125 -0
- package/dist/plugins/secrets/azure-keyvault.d.ts +12 -0
- package/dist/plugins/secrets/azure-keyvault.js +178 -0
- package/dist/plugins/secrets/env.d.ts +24 -0
- package/dist/plugins/secrets/env.js +62 -0
- package/dist/plugins/secrets/gcp-secret-manager.d.ts +12 -0
- package/dist/plugins/secrets/gcp-secret-manager.js +157 -0
- package/dist/templates/application/go/go.mod.template +4 -0
- package/dist/templates/application/go/main.template +8 -0
- package/dist/templates/application/go/test.template +11 -0
- package/dist/templates/application/javascript/main.template +14 -0
- package/dist/templates/application/javascript/test.template +8 -0
- package/dist/templates/application/python/main.template +13 -0
- package/dist/templates/application/python/requirements.txt.template +3 -0
- package/dist/templates/application/python/test.template +8 -0
- package/dist/templates/application/typescript/main.template +14 -0
- package/dist/templates/application/typescript/test.template +8 -0
- package/dist/templates/application/typescript/tsconfig.json.template +20 -0
- package/dist/templates/config/README.md.template +82 -0
- package/dist/templates/config/env.example.template +22 -0
- package/dist/templates/config/gitignore.template +40 -0
- package/dist/templates/config/tfwconfig.yml.template +69 -0
- package/dist/templates/templates/application/go/go.mod.template +4 -0
- package/dist/templates/templates/application/go/main.template +8 -0
- package/dist/templates/templates/application/go/test.template +11 -0
- package/dist/templates/templates/application/javascript/main.template +14 -0
- package/dist/templates/templates/application/javascript/test.template +8 -0
- package/dist/templates/templates/application/python/main.template +13 -0
- package/dist/templates/templates/application/python/requirements.txt.template +3 -0
- package/dist/templates/templates/application/python/test.template +8 -0
- package/dist/templates/templates/application/typescript/main.template +14 -0
- package/dist/templates/templates/application/typescript/test.template +8 -0
- package/dist/templates/templates/application/typescript/tsconfig.json.template +20 -0
- package/dist/templates/templates/config/README.md.template +82 -0
- package/dist/templates/templates/config/env.example.template +22 -0
- package/dist/templates/templates/config/gitignore.template +40 -0
- package/dist/templates/templates/config/tfwconfig.yml.template +69 -0
- package/dist/templates/templates/terraform/aws/_init.tf.template +24 -0
- package/dist/templates/templates/terraform/aws/inputs.tf.template +11 -0
- package/dist/templates/templates/terraform/azure/_init.tf.template +19 -0
- package/dist/templates/templates/terraform/azure/inputs.tf.template +11 -0
- package/dist/templates/templates/terraform/gcp/_init.tf.template +20 -0
- package/dist/templates/templates/terraform/gcp/inputs.tf.template +16 -0
- package/dist/templates/templates/terraform/locals.tf.template +9 -0
- package/dist/templates/templates/terraform/main.tf.template +8 -0
- package/dist/templates/templates/terraform/modules/inputs.tf.template +5 -0
- package/dist/templates/templates/terraform/modules/main.tf.template +2 -0
- package/dist/templates/templates/terraform/modules/outputs.tf.template +2 -0
- package/dist/templates/templates/terraform/outputs.tf.template +6 -0
- package/dist/templates/terraform/aws/_init.tf.template +24 -0
- package/dist/templates/terraform/aws/inputs.tf.template +11 -0
- package/dist/templates/terraform/azure/_init.tf.template +19 -0
- package/dist/templates/terraform/azure/inputs.tf.template +11 -0
- package/dist/templates/terraform/gcp/_init.tf.template +20 -0
- package/dist/templates/terraform/gcp/inputs.tf.template +16 -0
- package/dist/templates/terraform/locals.tf.template +9 -0
- package/dist/templates/terraform/main.tf.template +8 -0
- package/dist/templates/terraform/modules/inputs.tf.template +5 -0
- package/dist/templates/terraform/modules/main.tf.template +2 -0
- package/dist/templates/terraform/modules/outputs.tf.template +2 -0
- package/dist/templates/terraform/outputs.tf.template +6 -0
- package/dist/types/config.d.ts +92 -0
- package/dist/types/config.js +6 -0
- package/dist/types/context.d.ts +59 -0
- package/dist/types/context.js +6 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.js +23 -0
- package/dist/types/plugins.d.ts +77 -0
- package/dist/types/plugins.js +6 -0
- package/dist/utils/cloud.d.ts +43 -0
- package/dist/utils/cloud.js +150 -0
- package/dist/utils/git.d.ts +88 -0
- package/dist/utils/git.js +258 -0
- package/dist/utils/logger.d.ts +67 -0
- package/dist/utils/logger.js +121 -0
- package/dist/utils/scaffolding.d.ts +92 -0
- package/dist/utils/scaffolding.js +338 -0
- package/dist/utils/templates.d.ts +25 -0
- package/dist/utils/templates.js +70 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Terraflow CLI - Main entry point
|
|
5
|
+
* An opinionated Terraform workflow CLI with multi-cloud support
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const config_1 = require("./core/config");
|
|
12
|
+
const context_1 = require("./core/context");
|
|
13
|
+
const terraform_1 = require("./core/terraform");
|
|
14
|
+
const config_2 = require("./commands/config");
|
|
15
|
+
const init_1 = require("./commands/init");
|
|
16
|
+
const logger_1 = require("./utils/logger");
|
|
17
|
+
const program = new commander_1.Command();
|
|
18
|
+
/**
|
|
19
|
+
* Main CLI entry point
|
|
20
|
+
*/
|
|
21
|
+
async function main() {
|
|
22
|
+
// Load package.json for version
|
|
23
|
+
let version = '1.0.0';
|
|
24
|
+
try {
|
|
25
|
+
const packageJsonPath = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
26
|
+
const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf8'));
|
|
27
|
+
version = packageJson.version || '1.0.0';
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Use default version if package.json can't be read
|
|
31
|
+
}
|
|
32
|
+
// Set up program
|
|
33
|
+
program
|
|
34
|
+
.name('terraflow')
|
|
35
|
+
.description('Opinionated Terraform workflow CLI with multi-cloud support')
|
|
36
|
+
.version(version, '-V, --version', 'Show version number')
|
|
37
|
+
.allowExcessArguments(true) // Allow terraform arguments to pass through
|
|
38
|
+
.passThroughOptions(); // Pass unknown options through to terraform
|
|
39
|
+
// Global options
|
|
40
|
+
program
|
|
41
|
+
.option('-c, --config <path>', 'Path to config file (default: <working-dir>/.tfwconfig.yml)')
|
|
42
|
+
.option('-w, --workspace <name>', 'Override workspace name')
|
|
43
|
+
.option('-b, --backend <type>', 'Backend type: local|s3|azurerm|gcs (default: local)')
|
|
44
|
+
.option('-s, --secrets <type>', 'Secrets provider: env|aws-secrets|azure-keyvault|gcp-secret-manager')
|
|
45
|
+
.option('--skip-commit-check', 'Skip git commit validation')
|
|
46
|
+
.option('-d, --working-dir <path>', 'Terraform working directory (default: ./terraform)')
|
|
47
|
+
.option('--assume-role <arn>', 'AWS role ARN to assume (AWS only)')
|
|
48
|
+
.option('-v, --verbose', 'Verbose logging')
|
|
49
|
+
.option('--debug', 'Debug logging (includes terraform debug output)')
|
|
50
|
+
.option('--dry-run', 'Show what would be executed without running')
|
|
51
|
+
.option('--no-color', 'Disable colored output');
|
|
52
|
+
// Special config command
|
|
53
|
+
const configCommand = program.command('config').description('Manage Terraflow configuration');
|
|
54
|
+
configCommand
|
|
55
|
+
.command('show')
|
|
56
|
+
.description('Show resolved configuration with source tracking and masked sensitive values')
|
|
57
|
+
.action(async () => {
|
|
58
|
+
try {
|
|
59
|
+
const opts = program.opts();
|
|
60
|
+
await config_2.ConfigCommand.show(opts);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
configCommand
|
|
67
|
+
.command('init')
|
|
68
|
+
.description('Generate skeleton config file with examples for all backend types, secrets providers, and auth configurations')
|
|
69
|
+
.option('-o, --output <file>', 'Output file path (default: .tfwconfig.yml in working directory)')
|
|
70
|
+
.action(async (options) => {
|
|
71
|
+
try {
|
|
72
|
+
await config_2.ConfigCommand.init(options.output);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// Init command for project scaffolding
|
|
79
|
+
program
|
|
80
|
+
.command('init [project-name]')
|
|
81
|
+
.description('Scaffold a new infrastructure project with opinionated defaults')
|
|
82
|
+
.option('-p, --provider <name>', 'Cloud provider: aws, azure, or gcp (default: aws)', 'aws')
|
|
83
|
+
.option('-l, --language <name>', 'Application language: javascript, typescript, python, or go (default: javascript)', 'javascript')
|
|
84
|
+
.option('-d, --working-dir <path>', 'Directory where to create the project (default: current directory)', process.cwd())
|
|
85
|
+
.option('-f, --force', 'Overwrite existing files if present (default: false)', false)
|
|
86
|
+
.addHelpText('after', `
|
|
87
|
+
Examples:
|
|
88
|
+
$ terraflow init my-project
|
|
89
|
+
$ terraflow init my-project --provider azure --language typescript
|
|
90
|
+
$ terraflow init --provider gcp --language python
|
|
91
|
+
$ terraflow init my-project --force
|
|
92
|
+
`)
|
|
93
|
+
.action(async (projectName, options) => {
|
|
94
|
+
try {
|
|
95
|
+
await init_1.InitCommand.execute(projectName, {
|
|
96
|
+
provider: options.provider,
|
|
97
|
+
language: options.language,
|
|
98
|
+
workingDir: options.workingDir,
|
|
99
|
+
force: options.force,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
logger_1.Logger.error(`Failed to initialize project: ${error instanceof Error ? error.message : String(error)}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// Parse arguments
|
|
108
|
+
program.parse();
|
|
109
|
+
// Check if config or init command was executed (handle early to avoid loading config)
|
|
110
|
+
const command = program.args[0];
|
|
111
|
+
if (command === 'config' || command === 'init') {
|
|
112
|
+
// Config and init commands handle their own execution, so we're done here
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Get parsed options
|
|
116
|
+
const opts = program.opts();
|
|
117
|
+
// Configure logger
|
|
118
|
+
if (opts.debug) {
|
|
119
|
+
logger_1.Logger.setLevel('debug');
|
|
120
|
+
}
|
|
121
|
+
else if (opts.verbose) {
|
|
122
|
+
logger_1.Logger.setLevel('info');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
logger_1.Logger.setLevel('info');
|
|
126
|
+
}
|
|
127
|
+
if (opts.noColor) {
|
|
128
|
+
logger_1.Logger.setColor(false);
|
|
129
|
+
}
|
|
130
|
+
// If no terraform command specified, show help
|
|
131
|
+
const terraformArgs = program.args;
|
|
132
|
+
if (terraformArgs.length === 0) {
|
|
133
|
+
program.help();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Load configuration
|
|
137
|
+
let config;
|
|
138
|
+
try {
|
|
139
|
+
config = await config_1.ConfigManager.load(opts);
|
|
140
|
+
logger_1.Logger.debug('Configuration loaded successfully');
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
logger_1.Logger.error(`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
// Update logger level from config if set
|
|
147
|
+
if (config.logging?.level) {
|
|
148
|
+
logger_1.Logger.setLevel(config.logging.level);
|
|
149
|
+
}
|
|
150
|
+
// Build execution context
|
|
151
|
+
let context;
|
|
152
|
+
try {
|
|
153
|
+
context = await context_1.ContextBuilder.build(config);
|
|
154
|
+
logger_1.Logger.debug(`Execution context built for workspace: ${context.workspace}`);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
logger_1.Logger.error(`Failed to build execution context: ${error instanceof Error ? error.message : String(error)}`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
// Validations are now run inside TerraformExecutor.execute()
|
|
161
|
+
// along with environment setup and plugin execution
|
|
162
|
+
// Execute terraform command
|
|
163
|
+
try {
|
|
164
|
+
const terraformCommand = terraformArgs[0] || '';
|
|
165
|
+
const terraformCommandArgs = terraformArgs.slice(1);
|
|
166
|
+
await terraform_1.TerraformExecutor.execute(terraformCommand, terraformCommandArgs, config, context, {
|
|
167
|
+
skipCommitCheck: opts.skipCommitCheck || config['skip-commit-check'] || false,
|
|
168
|
+
dryRun: opts.dryRun || false,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
logger_1.Logger.error(`Execution failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Run main
|
|
177
|
+
main().catch((error) => {
|
|
178
|
+
logger_1.Logger.error(`Unhandled error: ${error instanceof Error ? error.message : String(error)}`);
|
|
179
|
+
if (error instanceof Error && error.stack) {
|
|
180
|
+
logger_1.Logger.debug(error.stack);
|
|
181
|
+
}
|
|
182
|
+
process.exit(1);
|
|
183
|
+
});
|
|
184
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS assume role auth plugin
|
|
3
|
+
* Assumes an AWS IAM role and returns temporary credentials
|
|
4
|
+
*/
|
|
5
|
+
import type { AuthPlugin } from '../../types';
|
|
6
|
+
/**
|
|
7
|
+
* AWS assume role authentication plugin
|
|
8
|
+
*/
|
|
9
|
+
export declare const awsAssumeRole: AuthPlugin;
|
|
10
|
+
//# sourceMappingURL=aws-assume-role.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AWS assume role auth plugin
|
|
4
|
+
* Assumes an AWS IAM role and returns temporary credentials
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.awsAssumeRole = void 0;
|
|
8
|
+
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
9
|
+
const errors_1 = require("../../core/errors");
|
|
10
|
+
const logger_1 = require("../../utils/logger");
|
|
11
|
+
/**
|
|
12
|
+
* AWS IAM Role ARN format validation
|
|
13
|
+
* Format: arn:aws:iam::[0-9]{12}:role/[a-zA-Z0-9+=,.@_-]+
|
|
14
|
+
*/
|
|
15
|
+
const IAM_ROLE_ARN_REGEX = /^arn:aws:iam::\d{12}:role\/[a-zA-Z0-9+=,.@_-]+$/;
|
|
16
|
+
/**
|
|
17
|
+
* AWS assume role authentication plugin
|
|
18
|
+
*/
|
|
19
|
+
exports.awsAssumeRole = {
|
|
20
|
+
name: 'aws-assume-role',
|
|
21
|
+
/**
|
|
22
|
+
* Validate AWS assume role configuration
|
|
23
|
+
* @param config - Auth configuration
|
|
24
|
+
* @throws {ConfigError} If configuration is invalid
|
|
25
|
+
*/
|
|
26
|
+
validate: async (config) => {
|
|
27
|
+
if (!config.assume_role) {
|
|
28
|
+
throw new errors_1.ConfigError('AWS assume role configuration is required');
|
|
29
|
+
}
|
|
30
|
+
const assumeRoleConfig = config.assume_role;
|
|
31
|
+
// role_arn is required
|
|
32
|
+
if (!assumeRoleConfig.role_arn) {
|
|
33
|
+
throw new errors_1.ConfigError('AWS assume role requires "role_arn" configuration');
|
|
34
|
+
}
|
|
35
|
+
// Validate role_arn format
|
|
36
|
+
if (!IAM_ROLE_ARN_REGEX.test(assumeRoleConfig.role_arn)) {
|
|
37
|
+
throw new errors_1.ConfigError(`Invalid role_arn format: ${assumeRoleConfig.role_arn}. Expected format: arn:aws:iam::123456789012:role/RoleName`);
|
|
38
|
+
}
|
|
39
|
+
// Validate duration if provided (must be between 900 and 43200 seconds)
|
|
40
|
+
if (assumeRoleConfig.duration !== undefined) {
|
|
41
|
+
if (assumeRoleConfig.duration < 900 || assumeRoleConfig.duration > 43200) {
|
|
42
|
+
throw new errors_1.ConfigError(`Invalid duration: ${assumeRoleConfig.duration}. Duration must be between 900 and 43200 seconds (15 minutes to 12 hours)`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
/**
|
|
47
|
+
* Authenticate by assuming an AWS IAM role
|
|
48
|
+
* Returns temporary credentials as environment variables
|
|
49
|
+
* @param config - Auth configuration
|
|
50
|
+
* @param context - Execution context
|
|
51
|
+
* @returns Temporary credentials as environment variables
|
|
52
|
+
*/
|
|
53
|
+
authenticate: async (config, context) => {
|
|
54
|
+
if (!config.assume_role) {
|
|
55
|
+
throw new errors_1.ConfigError('AWS assume role configuration is required');
|
|
56
|
+
}
|
|
57
|
+
const assumeRoleConfig = config.assume_role;
|
|
58
|
+
const roleArn = assumeRoleConfig.role_arn;
|
|
59
|
+
const sessionName = assumeRoleConfig.session_name || 'terraflow-session';
|
|
60
|
+
const durationSeconds = assumeRoleConfig.duration || 3600;
|
|
61
|
+
try {
|
|
62
|
+
logger_1.Logger.debug(`Assuming AWS IAM role: ${roleArn}`);
|
|
63
|
+
// Create STS client
|
|
64
|
+
const stsClient = new client_sts_1.STSClient({
|
|
65
|
+
region: context.cloud.awsRegion || process.env.AWS_REGION || 'us-east-1',
|
|
66
|
+
});
|
|
67
|
+
// Call AssumeRole
|
|
68
|
+
const command = new client_sts_1.AssumeRoleCommand({
|
|
69
|
+
RoleArn: roleArn,
|
|
70
|
+
RoleSessionName: sessionName,
|
|
71
|
+
DurationSeconds: durationSeconds,
|
|
72
|
+
});
|
|
73
|
+
const response = await stsClient.send(command);
|
|
74
|
+
if (!response.Credentials) {
|
|
75
|
+
throw new Error('AssumeRole response did not contain credentials');
|
|
76
|
+
}
|
|
77
|
+
const credentials = response.Credentials;
|
|
78
|
+
// Return credentials as environment variables
|
|
79
|
+
const envVars = {
|
|
80
|
+
AWS_ACCESS_KEY_ID: credentials.AccessKeyId || '',
|
|
81
|
+
AWS_SECRET_ACCESS_KEY: credentials.SecretAccessKey || '',
|
|
82
|
+
AWS_SESSION_TOKEN: credentials.SessionToken || '',
|
|
83
|
+
};
|
|
84
|
+
// Set expiration time if available (optional, for information)
|
|
85
|
+
if (credentials.Expiration) {
|
|
86
|
+
logger_1.Logger.debug(`Credentials expire at: ${credentials.Expiration.toISOString()}`);
|
|
87
|
+
}
|
|
88
|
+
logger_1.Logger.info(`✅ Successfully assumed role: ${roleArn}`);
|
|
89
|
+
return envVars;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
// Handle specific AWS errors
|
|
93
|
+
if (error instanceof Error) {
|
|
94
|
+
if (error.name === 'AccessDenied' || error.message.includes('AccessDenied')) {
|
|
95
|
+
throw new errors_1.ConfigError(`Access denied when assuming role ${roleArn}. Ensure your AWS credentials have permission to assume this role.`);
|
|
96
|
+
}
|
|
97
|
+
if (error.message.includes('NoSuchEntity')) {
|
|
98
|
+
throw new errors_1.ConfigError(`Role ${roleArn} does not exist.`);
|
|
99
|
+
}
|
|
100
|
+
if (error.message.includes('MalformedPolicyDocument')) {
|
|
101
|
+
throw new errors_1.ConfigError(`Invalid role configuration for ${roleArn}. Check the role's trust policy.`);
|
|
102
|
+
}
|
|
103
|
+
// Generic error handling
|
|
104
|
+
throw new errors_1.ConfigError(`Failed to assume role ${roleArn}: ${error.message}. Ensure you have valid AWS credentials and permissions.`);
|
|
105
|
+
}
|
|
106
|
+
throw new errors_1.ConfigError(`Failed to assume role ${roleArn}: ${String(error)}`);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=aws-assume-role.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure service principal auth plugin
|
|
3
|
+
* Authenticates using Azure service principal and returns ARM_* environment variables
|
|
4
|
+
*/
|
|
5
|
+
import type { AuthPlugin } from '../../types';
|
|
6
|
+
/**
|
|
7
|
+
* Azure service principal authentication plugin
|
|
8
|
+
*/
|
|
9
|
+
export declare const azureServicePrincipal: AuthPlugin;
|
|
10
|
+
//# sourceMappingURL=azure-service-principal.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Azure service principal auth plugin
|
|
4
|
+
* Authenticates using Azure service principal and returns ARM_* environment variables
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.azureServicePrincipal = void 0;
|
|
8
|
+
const errors_1 = require("../../core/errors");
|
|
9
|
+
const logger_1 = require("../../utils/logger");
|
|
10
|
+
/**
|
|
11
|
+
* Azure service principal authentication plugin
|
|
12
|
+
*/
|
|
13
|
+
exports.azureServicePrincipal = {
|
|
14
|
+
name: 'azure-service-principal',
|
|
15
|
+
/**
|
|
16
|
+
* Validate Azure service principal configuration
|
|
17
|
+
* @param config - Auth configuration
|
|
18
|
+
* @throws {ConfigError} If configuration is invalid
|
|
19
|
+
*/
|
|
20
|
+
validate: async (config) => {
|
|
21
|
+
if (!config.service_principal) {
|
|
22
|
+
throw new errors_1.ConfigError('Azure service principal configuration is required');
|
|
23
|
+
}
|
|
24
|
+
const spConfig = config.service_principal;
|
|
25
|
+
// client_id is required
|
|
26
|
+
if (!spConfig.client_id) {
|
|
27
|
+
throw new errors_1.ConfigError('Azure service principal requires "client_id" configuration');
|
|
28
|
+
}
|
|
29
|
+
// tenant_id is required
|
|
30
|
+
if (!spConfig.tenant_id) {
|
|
31
|
+
throw new errors_1.ConfigError('Azure service principal requires "tenant_id" configuration');
|
|
32
|
+
}
|
|
33
|
+
// client_secret is optional if using managed identity or certificate
|
|
34
|
+
// But if provided, it should not be empty
|
|
35
|
+
if (spConfig.client_secret !== undefined && !spConfig.client_secret) {
|
|
36
|
+
throw new errors_1.ConfigError('Azure service principal "client_secret" cannot be empty if provided');
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
/**
|
|
40
|
+
* Authenticate using Azure service principal
|
|
41
|
+
* Returns ARM_* environment variables for Terraform Azure provider
|
|
42
|
+
* @param config - Auth configuration
|
|
43
|
+
* @param context - Execution context
|
|
44
|
+
* @returns Environment variables for Azure authentication
|
|
45
|
+
*/
|
|
46
|
+
authenticate: async (config, context) => {
|
|
47
|
+
if (!config.service_principal) {
|
|
48
|
+
throw new errors_1.ConfigError('Azure service principal configuration is required');
|
|
49
|
+
}
|
|
50
|
+
const spConfig = config.service_principal;
|
|
51
|
+
const clientId = spConfig.client_id;
|
|
52
|
+
const tenantId = spConfig.tenant_id;
|
|
53
|
+
const clientSecret = spConfig.client_secret;
|
|
54
|
+
try {
|
|
55
|
+
// Validate credentials using Azure CLI (if client_secret is provided)
|
|
56
|
+
if (clientSecret) {
|
|
57
|
+
logger_1.Logger.debug(`Validating Azure service principal: ${clientId}`);
|
|
58
|
+
// Note: We can't easily validate credentials without making an actual API call
|
|
59
|
+
// The validation will happen when Terraform tries to use these credentials
|
|
60
|
+
// For now, we just validate that the values are provided
|
|
61
|
+
if (!clientId || !tenantId || !clientSecret) {
|
|
62
|
+
throw new errors_1.ConfigError('Azure service principal requires client_id, tenant_id, and client_secret');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// No client_secret provided - assume using managed identity or certificate
|
|
67
|
+
logger_1.Logger.debug(`Azure service principal configured without client_secret. Assuming managed identity or certificate authentication.`);
|
|
68
|
+
}
|
|
69
|
+
// Build environment variables for Terraform Azure provider
|
|
70
|
+
const envVars = {
|
|
71
|
+
ARM_CLIENT_ID: clientId,
|
|
72
|
+
ARM_TENANT_ID: tenantId,
|
|
73
|
+
};
|
|
74
|
+
// Add subscription ID if available from context
|
|
75
|
+
if (context.cloud.azureSubscriptionId) {
|
|
76
|
+
envVars.ARM_SUBSCRIPTION_ID = context.cloud.azureSubscriptionId;
|
|
77
|
+
}
|
|
78
|
+
else if (process.env.ARM_SUBSCRIPTION_ID) {
|
|
79
|
+
envVars.ARM_SUBSCRIPTION_ID = process.env.ARM_SUBSCRIPTION_ID;
|
|
80
|
+
}
|
|
81
|
+
// Add client_secret if provided
|
|
82
|
+
if (clientSecret) {
|
|
83
|
+
envVars.ARM_CLIENT_SECRET = clientSecret;
|
|
84
|
+
}
|
|
85
|
+
logger_1.Logger.info(`✅ Successfully authenticated Azure service principal: ${clientId}`);
|
|
86
|
+
return envVars;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof errors_1.ConfigError) {
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
if (error instanceof Error) {
|
|
93
|
+
throw new errors_1.ConfigError(`Failed to authenticate Azure service principal: ${error.message}. Ensure client_id, tenant_id, and client_secret (if required) are correct.`);
|
|
94
|
+
}
|
|
95
|
+
throw new errors_1.ConfigError(`Failed to authenticate Azure service principal: ${String(error)}`);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=azure-service-principal.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GCP service account auth plugin
|
|
3
|
+
* Authenticates using GCP service account key file and returns GCP credentials environment variables
|
|
4
|
+
*/
|
|
5
|
+
import type { AuthPlugin } from '../../types';
|
|
6
|
+
/**
|
|
7
|
+
* GCP service account authentication plugin
|
|
8
|
+
*/
|
|
9
|
+
export declare const gcpServiceAccount: AuthPlugin;
|
|
10
|
+
//# sourceMappingURL=gcp-service-account.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GCP service account auth plugin
|
|
4
|
+
* Authenticates using GCP service account key file and returns GCP credentials environment variables
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.gcpServiceAccount = void 0;
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const errors_1 = require("../../core/errors");
|
|
10
|
+
const logger_1 = require("../../utils/logger");
|
|
11
|
+
/**
|
|
12
|
+
* GCP service account authentication plugin
|
|
13
|
+
*/
|
|
14
|
+
exports.gcpServiceAccount = {
|
|
15
|
+
name: 'gcp-service-account',
|
|
16
|
+
/**
|
|
17
|
+
* Validate GCP service account configuration
|
|
18
|
+
* @param config - Auth configuration
|
|
19
|
+
* @throws {ConfigError} If configuration is invalid
|
|
20
|
+
*/
|
|
21
|
+
validate: async (config) => {
|
|
22
|
+
if (!config.service_account) {
|
|
23
|
+
throw new errors_1.ConfigError('GCP service account configuration is required');
|
|
24
|
+
}
|
|
25
|
+
const saConfig = config.service_account;
|
|
26
|
+
// key_file is required
|
|
27
|
+
if (!saConfig.key_file) {
|
|
28
|
+
throw new errors_1.ConfigError('GCP service account requires "key_file" configuration');
|
|
29
|
+
}
|
|
30
|
+
// Check if key file exists
|
|
31
|
+
if (!(0, fs_1.existsSync)(saConfig.key_file)) {
|
|
32
|
+
throw new errors_1.ConfigError(`GCP service account key file does not exist: ${saConfig.key_file}`);
|
|
33
|
+
}
|
|
34
|
+
// Try to read and parse the key file to validate it's valid JSON
|
|
35
|
+
try {
|
|
36
|
+
const keyFileContent = (0, fs_1.readFileSync)(saConfig.key_file, 'utf8');
|
|
37
|
+
const keyData = JSON.parse(keyFileContent);
|
|
38
|
+
// Validate it's a service account key file
|
|
39
|
+
if (!keyData.type || keyData.type !== 'service_account') {
|
|
40
|
+
throw new errors_1.ConfigError(`Invalid GCP service account key file: ${saConfig.key_file}. Expected type "service_account".`);
|
|
41
|
+
}
|
|
42
|
+
if (!keyData.project_id) {
|
|
43
|
+
logger_1.Logger.warn(`GCP service account key file "${saConfig.key_file}" does not contain project_id. Project ID will need to be set via GCLOUD_PROJECT environment variable.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof errors_1.ConfigError) {
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
if (error instanceof SyntaxError) {
|
|
51
|
+
throw new errors_1.ConfigError(`GCP service account key file "${saConfig.key_file}" is not valid JSON: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
throw new errors_1.ConfigError(`Failed to read GCP service account key file "${saConfig.key_file}": ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* Authenticate using GCP service account key file
|
|
58
|
+
* Returns GCP credentials environment variables
|
|
59
|
+
* @param config - Auth configuration
|
|
60
|
+
* @param context - Execution context
|
|
61
|
+
* @returns Environment variables for GCP authentication
|
|
62
|
+
*/
|
|
63
|
+
authenticate: async (config, context) => {
|
|
64
|
+
if (!config.service_account) {
|
|
65
|
+
throw new errors_1.ConfigError('GCP service account configuration is required');
|
|
66
|
+
}
|
|
67
|
+
const saConfig = config.service_account;
|
|
68
|
+
const keyFilePath = saConfig.key_file;
|
|
69
|
+
try {
|
|
70
|
+
// Read and parse key file
|
|
71
|
+
const keyFileContent = (0, fs_1.readFileSync)(keyFilePath, 'utf8');
|
|
72
|
+
const keyData = JSON.parse(keyFileContent);
|
|
73
|
+
// Build environment variables for GCP authentication
|
|
74
|
+
const envVars = {
|
|
75
|
+
GOOGLE_APPLICATION_CREDENTIALS: keyFilePath,
|
|
76
|
+
};
|
|
77
|
+
// Add project ID from key file, context, or leave undefined (GCP SDK will use default)
|
|
78
|
+
const projectId = keyData.project_id ||
|
|
79
|
+
context.cloud.gcpProjectId ||
|
|
80
|
+
process.env.GCLOUD_PROJECT ||
|
|
81
|
+
process.env.GCP_PROJECT ||
|
|
82
|
+
process.env.GOOGLE_CLOUD_PROJECT;
|
|
83
|
+
if (projectId) {
|
|
84
|
+
envVars.GCLOUD_PROJECT = projectId;
|
|
85
|
+
envVars.GCP_PROJECT = projectId;
|
|
86
|
+
envVars.GOOGLE_CLOUD_PROJECT = projectId;
|
|
87
|
+
}
|
|
88
|
+
logger_1.Logger.info(`✅ Successfully configured GCP service account from: ${keyFilePath}`);
|
|
89
|
+
return envVars;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (error instanceof errors_1.ConfigError) {
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
if (error instanceof SyntaxError) {
|
|
96
|
+
throw new errors_1.ConfigError(`GCP service account key file "${keyFilePath}" is not valid JSON: ${error.message}`);
|
|
97
|
+
}
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
throw new errors_1.ConfigError(`Failed to read GCP service account key file "${keyFilePath}": ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
throw new errors_1.ConfigError(`Failed to read GCP service account key file "${keyFilePath}": ${String(error)}`);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=gcp-service-account.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure RM backend plugin
|
|
3
|
+
* Handles Azure Resource Manager Terraform state backend
|
|
4
|
+
*/
|
|
5
|
+
import type { BackendPlugin } from '../../types';
|
|
6
|
+
/**
|
|
7
|
+
* Azure RM backend plugin
|
|
8
|
+
*/
|
|
9
|
+
export declare const azurermBackend: BackendPlugin;
|
|
10
|
+
//# sourceMappingURL=azurerm.d.ts.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Azure RM backend plugin
|
|
4
|
+
* Handles Azure Resource Manager Terraform state backend
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.azurermBackend = void 0;
|
|
8
|
+
const errors_1 = require("../../core/errors");
|
|
9
|
+
const logger_1 = require("../../utils/logger");
|
|
10
|
+
const templates_1 = require("../../utils/templates");
|
|
11
|
+
/**
|
|
12
|
+
* Azure RM backend plugin
|
|
13
|
+
*/
|
|
14
|
+
exports.azurermBackend = {
|
|
15
|
+
name: 'azurerm',
|
|
16
|
+
/**
|
|
17
|
+
* Validate Azure RM backend configuration
|
|
18
|
+
* @param config - Backend configuration
|
|
19
|
+
* @throws {ConfigError} If configuration is invalid
|
|
20
|
+
*/
|
|
21
|
+
validate: async (config) => {
|
|
22
|
+
if (!config.config) {
|
|
23
|
+
throw new errors_1.ConfigError('Azure RM backend requires configuration');
|
|
24
|
+
}
|
|
25
|
+
const azureConfig = config.config;
|
|
26
|
+
// Required fields
|
|
27
|
+
if (!azureConfig.storage_account_name) {
|
|
28
|
+
throw new errors_1.ConfigError('Azure RM backend requires "storage_account_name" configuration');
|
|
29
|
+
}
|
|
30
|
+
if (!azureConfig.container_name) {
|
|
31
|
+
throw new errors_1.ConfigError('Azure RM backend requires "container_name" configuration');
|
|
32
|
+
}
|
|
33
|
+
if (!azureConfig.key) {
|
|
34
|
+
throw new errors_1.ConfigError('Azure RM backend requires "key" configuration');
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* Generate Terraform backend configuration arguments
|
|
39
|
+
* @param config - Backend configuration
|
|
40
|
+
* @param context - Execution context (for template variable resolution)
|
|
41
|
+
* @returns Array of -backend-config arguments for terraform init
|
|
42
|
+
*/
|
|
43
|
+
getBackendConfig: async (config, context) => {
|
|
44
|
+
if (!config.config) {
|
|
45
|
+
throw new errors_1.ConfigError('Azure RM backend requires configuration');
|
|
46
|
+
}
|
|
47
|
+
// Build template context from execution context
|
|
48
|
+
const templateVars = {
|
|
49
|
+
...context.templateVars,
|
|
50
|
+
};
|
|
51
|
+
// Add cloud-specific variables
|
|
52
|
+
if (context.cloud.azureSubscriptionId) {
|
|
53
|
+
templateVars.AZURE_SUBSCRIPTION_ID = context.cloud.azureSubscriptionId;
|
|
54
|
+
}
|
|
55
|
+
if (context.cloud.azureTenantId) {
|
|
56
|
+
templateVars.AZURE_TENANT_ID = context.cloud.azureTenantId;
|
|
57
|
+
}
|
|
58
|
+
// Resolve template variables in config
|
|
59
|
+
const resolvedConfig = templates_1.TemplateUtils.resolveObject(config.config, templateVars);
|
|
60
|
+
const azureConfig = resolvedConfig;
|
|
61
|
+
// Build backend-config arguments
|
|
62
|
+
const backendArgs = [];
|
|
63
|
+
// Required fields
|
|
64
|
+
backendArgs.push(`-backend-config=storage_account_name=${azureConfig.storage_account_name}`);
|
|
65
|
+
backendArgs.push(`-backend-config=container_name=${azureConfig.container_name}`);
|
|
66
|
+
backendArgs.push(`-backend-config=key=${azureConfig.key}`);
|
|
67
|
+
// Optional fields
|
|
68
|
+
if (azureConfig.resource_group_name) {
|
|
69
|
+
backendArgs.push(`-backend-config=resource_group_name=${azureConfig.resource_group_name}`);
|
|
70
|
+
}
|
|
71
|
+
if (azureConfig.subscription_id) {
|
|
72
|
+
backendArgs.push(`-backend-config=subscription_id=${azureConfig.subscription_id}`);
|
|
73
|
+
}
|
|
74
|
+
if (azureConfig.tenant_id) {
|
|
75
|
+
backendArgs.push(`-backend-config=tenant_id=${azureConfig.tenant_id}`);
|
|
76
|
+
}
|
|
77
|
+
if (azureConfig.client_id) {
|
|
78
|
+
backendArgs.push(`-backend-config=client_id=${azureConfig.client_id}`);
|
|
79
|
+
}
|
|
80
|
+
if (azureConfig.client_secret) {
|
|
81
|
+
backendArgs.push(`-backend-config=client_secret=${azureConfig.client_secret}`);
|
|
82
|
+
}
|
|
83
|
+
if (azureConfig.client_certificate_path) {
|
|
84
|
+
backendArgs.push(`-backend-config=client_certificate_path=${azureConfig.client_certificate_path}`);
|
|
85
|
+
}
|
|
86
|
+
if (azureConfig.client_certificate_password) {
|
|
87
|
+
backendArgs.push(`-backend-config=client_certificate_password=${azureConfig.client_certificate_password}`);
|
|
88
|
+
}
|
|
89
|
+
if (azureConfig.use_msi !== undefined) {
|
|
90
|
+
backendArgs.push(`-backend-config=use_msi=${azureConfig.use_msi}`);
|
|
91
|
+
}
|
|
92
|
+
if (azureConfig.msi_endpoint) {
|
|
93
|
+
backendArgs.push(`-backend-config=msi_endpoint=${azureConfig.msi_endpoint}`);
|
|
94
|
+
}
|
|
95
|
+
if (azureConfig.environment) {
|
|
96
|
+
backendArgs.push(`-backend-config=environment=${azureConfig.environment}`);
|
|
97
|
+
}
|
|
98
|
+
if (azureConfig.endpoint) {
|
|
99
|
+
backendArgs.push(`-backend-config=endpoint=${azureConfig.endpoint}`);
|
|
100
|
+
}
|
|
101
|
+
if (azureConfig.sas_token) {
|
|
102
|
+
backendArgs.push(`-backend-config=sas_token=${azureConfig.sas_token}`);
|
|
103
|
+
}
|
|
104
|
+
if (azureConfig.access_key) {
|
|
105
|
+
backendArgs.push(`-backend-config=access_key=${azureConfig.access_key}`);
|
|
106
|
+
}
|
|
107
|
+
if (azureConfig.snapshot !== undefined) {
|
|
108
|
+
backendArgs.push(`-backend-config=snapshot=${azureConfig.snapshot}`);
|
|
109
|
+
}
|
|
110
|
+
if (azureConfig.encryption !== undefined) {
|
|
111
|
+
backendArgs.push(`-backend-config=encryption=${azureConfig.encryption}`);
|
|
112
|
+
}
|
|
113
|
+
logger_1.Logger.debug(`Generated ${backendArgs.length} backend-config arguments for Azure RM backend`);
|
|
114
|
+
return backendArgs;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=azurerm.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GCS backend plugin
|
|
3
|
+
* Handles Google Cloud Storage Terraform state backend
|
|
4
|
+
*/
|
|
5
|
+
import type { BackendPlugin } from '../../types';
|
|
6
|
+
/**
|
|
7
|
+
* GCS backend plugin
|
|
8
|
+
*/
|
|
9
|
+
export declare const gcsBackend: BackendPlugin;
|
|
10
|
+
//# sourceMappingURL=gcs.d.ts.map
|